mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-26 07:26:41 +00:00
Merge branch 'master' of ssh://sapsailing.com/home/trac/git into bug4232
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
Beschreibung des Skripts:
|
||||
|
||||
0. Voraussetzungen f�r Nutzung des Skripts
|
||||
|
||||
- AWS CLI (https://aws.amazon.com/de/cli)
|
||||
- Cygwin Packages:
|
||||
-- tmux
|
||||
-- jq
|
||||
-- openssh
|
||||
-- wget
|
||||
-- curl
|
||||
|
||||
0.1 AWS CLI konfigurieren
|
||||
|
||||
"aws configure" durchf�hren.
|
||||
Falls noch kein Keypair vorhanden:
|
||||
|
||||
IAM-Konsole >> Users >> dein Benutzername >> Security Credentials >> Create access key
|
||||
|
||||
0.2 Default-Werte anpassen
|
||||
|
||||
Default-Werte in "aws_variables.sh" anpassen (key_name, key_file), damit diese nicht
|
||||
immer neu eingegeben werden m�ssen. Testweise erst einmal in Region "eu-west-2" bleiben.
|
||||
|
||||
key_name: Name des Keypairs zur Verbindung zur Instanz
|
||||
key_file: Pfad zur Datei
|
||||
...
|
||||
|
||||
0.3 Skript ausprobieren
|
||||
|
||||
./aws-setup.sh --help
|
||||
./aws-setup instance-with-load-balancer
|
||||
|
||||
Falls Fehler auftaucht:
|
||||
Skript mit "-d"-Parameter starten und in den Logs der Instanz nachsehen
|
||||
(./aws-setup.sh --tail --public-dns-name ec2-x...amazon.com)
|
||||
|
||||
1. Aufbau:
|
||||
|
||||
1.1 Dateien und ihre Hauptaufgaben:
|
||||
|
||||
aws-setup-sh:
|
||||
Parameteraufnahme, Main-Funktion, Dokumentation der Bedienung, Sourcing von lib/utils.sh
|
||||
|
||||
lib/aws-functions.sh:
|
||||
Funktionen f�r AWS (z.B. Load Balancer erstellen) und App-Instanz (z.B. Event erstellen)
|
||||
|
||||
lib/aws-variables.sh:
|
||||
Festlegung von regionspezifischen Konstanten (z.B Hosted Zone ID) und Defaultwerten (z.B Region)
|
||||
|
||||
lib/scenario_instance.sh:
|
||||
Erstellung einer Instanz und anschlie�endes Hinzuf�gen zu einem Load Balancer der �ber einen Route53 Eintrag
|
||||
mit einer bestimmten Domain (z.B. wcs17.sapsailing.com) verkn�pft wird.
|
||||
|
||||
lib/scenario_tail.sh:
|
||||
Automatische Anzeige der Log-Dateien einer Instanz mithilfe von tmux
|
||||
|
||||
lib/tmux_functions.sh:
|
||||
Oberfl�chenkonstruktion, Vorbedingungstest, Befehlsausf�hrung
|
||||
|
||||
lib/util_functions.sh:
|
||||
Erleichterung der Benutzereingabe, Validierungsfunktionen, sonstige potenziell relevante Hilfsfunktionen
|
||||
|
||||
lib/util_variables.sh:
|
||||
Sonstiges Hilfsvariablen (Zeitstempel, Skriptname, etc.)
|
||||
|
||||
lib/utils.sh:
|
||||
Ausgabe von farbigen Meldungen, Logging (unbenutzt), Sourcing der restlichen Bash-Dateien im Verzeichnis
|
||||
|
||||
1.2 Szenarios
|
||||
|
||||
Szenarios sind gekapselte Ausf�hrungseinheiten, die durch Orchestrierung von AWS-Funktionen oder Bereitstellung
|
||||
eigener Funktionalit�t einen Mehrwert f�r die Automatisierung darstellen.
|
||||
|
||||
Szenarios beinhalten folgende Funktionen:
|
||||
|
||||
- Funktion zum Start des Ausf�hrung des Szenarios
|
||||
- Funktion zur �berpr�fung von Vorbedingungen (Abh�ngigkeiten von Paketen oder Umgebungsvariablen) [optional]
|
||||
- Funktion zur Sicherstellung der Initialisierung von ben�tigen Variablen
|
||||
- Funktion zur Ausf�hrung der Programmlogik
|
||||
|
||||
2. Funktionalit�t
|
||||
|
||||
2.1 Initialisierung von ben�tigten Variablen
|
||||
|
||||
Jedes Szenario ben�tigt f�r seine Ausf�hrung bestimmte inititalisierte Variablen. Die Zuweisung eines Werts zu einer Variable erfolgt
|
||||
entweder beim Start des Skripts �ber die Mitgabe eines Parameters oder falls kein Parameter �bergeben worden ist, �ber die Eingabe des
|
||||
Benutzers nach entsprechender Aufforderung.
|
||||
Wenn der Benutzer zur Eingabe eines Werts f�r eine Variable aufgefordert wird, wird automatisch ein Default-Wert aus der
|
||||
Datei "aws_variables.sh" vorgeschlagen. Dies erleichtert die Benutzereingabe, da sich bestimmte Werte, wie z.B. der Name
|
||||
des Keypairs zur Verbindung einer Instanz selten �ndern. In der Datei werden au�erdem regionspezifische Variablenwerte
|
||||
(z.B. IDs der Sicherheitsgruppen oder Images) festgelegt.
|
||||
|
||||
2.2 Fehlerbehandlung
|
||||
|
||||
Um die erfolgreiche Ausf�hrung einer Funktion zu �berpr�fen werden verschiedene Strategien verfolgt:
|
||||
|
||||
1. �berpr�fung des R�ckgabewerts einer Funktion
|
||||
|
||||
Diese Strategie wird bei AWS-spezifischen Funktionen angewandt. Bei Aufruf eines AWS-Kommandos (z.B. aws elb create-load-balancer) wird
|
||||
bei Erfolg der R�ckgabewert 0 zur�ckgegeben und bei Vorkommen eines Fehlers der R�ckgabewert 1.
|
||||
|
||||
2. �berpr�fen des HTTP-Codes
|
||||
|
||||
Bei Aufruf eines curl-Kommandos wird die Option -w "\n%{http_code}" mit�bergeben. Dabei werden Response- und HTTP-Code auf zwei Zeilen
|
||||
aufgeteilt und damit erm�glicht, dass nach Aufruf der Funktion die Zuordnung zu zwei verschiedenen Variablen erfolgt.
|
||||
|
||||
2.3 Meldungsausgabe
|
||||
|
||||
Innerhalb von Methoden wird der Nutzer �ber Erfolg oder Misserfolg benachrichtigt. Die Ausgabe erfolgt �ber den
|
||||
Standardfehlerkanal "stderr" (Workaround).
|
||||
|
||||
2.4 Tail mit tmux
|
||||
|
||||
Beim Verwenden des Parameters --tail werden automatisch relevante Log-Dateien angezeigt.
|
||||
Der Befehl ist in Kombination mit dem Parameter --public-dns-name oder auch mit dem Szenario
|
||||
--instance-with-load-balancer verwendbar. Zur Ausf�hrung muss das Skript in einer tmux-Session
|
||||
ausgef�hrt werden.
|
||||
|
||||
Beispiele:
|
||||
./aws-setup.sh --tail --public-dns-name ec2-xxx.eu-west-2.compute.amazonaws.com
|
||||
Zeigt relevante Log-Dateien der Instanz mit dem DNS Namen "ec2-xxx.eu-west-2.compute.amazonaws.com" an.
|
||||
|
||||
./aws-setup.sh --tail --instance-with-load-balancer
|
||||
Zeigt relevante Log-Dateien der Instanz die gerade erstellt wird, ab dem Zeitpunkt der Aufnahme der SSH-Verbindung
|
||||
|
||||
3. TODO
|
||||
|
||||
3.1 Fehlerbehebungen
|
||||
|
||||
- Nutzer erstellen scheitert bisher mit "Precondition failed" (Nutzer-
|
||||
name erf�llt Richtlinie nicht)
|
||||
|
||||
3.2 Verbesserungen
|
||||
|
||||
- run-instance Funktion nach "aws_functions.sh" faktorisieren
|
||||
- Anpassung der Dateierzeugung an Linux ("crlf" ersetzen)
|
||||
- Momentan wird der Admin-Nutzer als Eventersteller verwendet.
|
||||
Die Rechtevergabe an einen anderen Nutzer �ber die REST-API
|
||||
muss noch implementiert werden.
|
||||
|
||||
3.2 Erweiterungen
|
||||
|
||||
- list-Funktion mit Anzeige aller Instanznamen und deren DNS, Instanz-ID, etc. damit
|
||||
--tail nicht die Web-GUI erfordert
|
||||
- weitere Szenarios hinzuf�gen (Redeployment, R�ckmigration in Archiv, etc.)
|
||||
- regionspezifisches Sourcing von Dateien mit Variablen (Auskommentieren nicht mehr notwendig)
|
||||
- automatische Kontext�bernahme nach tmux
|
||||
|
||||
|
||||
Getestet unter Microsoft Windows [Version 10.0.10586]:
|
||||
- Cygwin 2.8.2(0.313/5/3)
|
||||
- aws-cli/1.11.129
|
||||
- tmux 2.8.2(0.313/5/3)
|
||||
- openssh 7.5p1-1
|
||||
- wget 1.19.1
|
||||
- curl 7.53.0
|
||||
|
||||
Funktioniert (Route53-Eintrag Erstellung konnte nicht getestet werden)
|
||||
|
||||
Getestet unter Microsoft Windows [Version 6.3.96001]:
|
||||
- Cygwin 2.9.0(0.318/5/3)
|
||||
- aws-cli/1.11.166
|
||||
- tmux 2.4
|
||||
- openssh 7.4p1
|
||||
- wget 1.19.1
|
||||
- curl 7.55.1-1
|
||||
|
||||
Funktioniert (Route53-Eintrag Erstellung konnte nicht getestet werden)
|
||||
Executable
+269
@@ -0,0 +1,269 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# AWS automation script for setting up SAP Sailing Analytics instance infrasctructure
|
||||
# ------------------------------------------------------
|
||||
|
||||
version="1.0.0" # Sets version variable
|
||||
|
||||
scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
utilsLocation="${scriptPath}/lib/utils.sh"
|
||||
|
||||
# source utils.sh which itself sources all other .sh files in the /lib directory
|
||||
if [ -f "${utilsLocation}" ]; then
|
||||
source "${utilsLocation}"
|
||||
else
|
||||
echo "Please find the file util.sh and add a reference to it in this script. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# delete temp files when trapped
|
||||
function trapCleanup() {
|
||||
echo ""
|
||||
deleteTemp
|
||||
die "Exit trapped. In function: '${FUNCNAME[*]}'"
|
||||
}
|
||||
|
||||
function safeExit() {
|
||||
deleteTemp
|
||||
trap - INT TERM EXIT
|
||||
exit
|
||||
}
|
||||
|
||||
function deleteTemp () {
|
||||
if is_dir "${tmpDir}"; then
|
||||
rm -r "${tmpDir}"
|
||||
fi
|
||||
}
|
||||
|
||||
quiet=false
|
||||
printLog=false
|
||||
verbose=true
|
||||
force=false
|
||||
strict=false
|
||||
debug=false
|
||||
args=()
|
||||
|
||||
# Create temp directory with three random numbers and the process ID
|
||||
# in the name. This directory is removed automatically at exit.
|
||||
tmpDir="./tmp.$RANDOM.$RANDOM.$RANDOM.$$/"
|
||||
(umask 000 && mkdir "${tmpDir}") || {
|
||||
die "Could not create temporary directory! Exiting."
|
||||
}
|
||||
|
||||
# Logging (not used)
|
||||
# To never save a logfile change variable to '/dev/null'
|
||||
# Save to standard user log location use: $HOME/Library/Logs/${scriptBasename}.log
|
||||
# -----------------------------------
|
||||
logFile="$HOME/Library/Logs/${scriptBasename}.log"
|
||||
|
||||
function mainScript() {
|
||||
echo -n
|
||||
|
||||
if $tail; then
|
||||
check_if_tmux_is_used
|
||||
fi
|
||||
|
||||
if $instance; then
|
||||
instance_start
|
||||
if $associate_clb; then
|
||||
associate_clb_start
|
||||
fi
|
||||
if $associate_elastic_ip; then
|
||||
associate_elastic_ip_start
|
||||
fi
|
||||
if $associate_alb; then
|
||||
associate_alb_start
|
||||
fi
|
||||
confirm_reset_panes
|
||||
safeExit
|
||||
fi
|
||||
|
||||
if $tail ; then
|
||||
tail_start
|
||||
confirm_reset_panes
|
||||
safeExit
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo -n "${scriptName} [OPTION]...
|
||||
|
||||
This is an AWS automation bash script for deploying SAP Sailing Analytics
|
||||
instances and their depending infrastructure
|
||||
|
||||
${bold}Parameter:${reset}
|
||||
-r, --region AWS region (e.g. \"eu-west-2\" for London)
|
||||
-t, --instance-type Instance type (e.g. \"t2.medium\")
|
||||
-k, --key-name IAM keypair name (e.g. \"leonradeck-keypair\")
|
||||
-i, --key-file Path to keypair file
|
||||
-s, --ssh-user SSH user to connect to instance (e.g. \"root\")
|
||||
-u, --user-username Username of user to create
|
||||
-q, --user-password Password of user to create
|
||||
-n, --instance-name Name for instance (e.g. \"WC Santander 2017\")
|
||||
-l, --instance-short-name Short name for instance (e.g. subdomain \"wcs17\")
|
||||
-a, --new-admin-password New password for the admin user
|
||||
-p, --public-dns-name Dns name of instance (e.g. \"ec2-35-176...amazonaws.com\")
|
||||
-f, --force Skip user input and use default variables
|
||||
-d, --debug Debug mode
|
||||
|
||||
${bold}Scenarios:${reset}
|
||||
--instance Create instance
|
||||
--associate-alb Associate instance with existing application load balancer whos
|
||||
listener is defined in variables_aws.sh. Automatically
|
||||
create necessary target group and host name rule.
|
||||
Currently *.dummy.sapsailing.com is used for test purposes.
|
||||
--associate-clb Associate instance with new classic load balancer.
|
||||
--asslociate-elastic-ip Associate instance with new elastic ip.
|
||||
--tail Tail logs from instance using tmux
|
||||
|
||||
${bold}Other:${reset}
|
||||
--version Output version information and exit
|
||||
|
||||
|
||||
${bold}Examples:${reset}
|
||||
Create instance:
|
||||
> ./aws-setup.sh --instance
|
||||
|
||||
Associate application load balancer:
|
||||
> ./aws-setup.sh --instance --associate-alb
|
||||
|
||||
Associate instance with classic load balancer while
|
||||
automatically tailing important log files (tmux required):
|
||||
> ./aws-setup.sh --instance --associate-clb --tail
|
||||
|
||||
Associate instance with elastic ip:
|
||||
> ./aws-setup.sh --instance --associate-elastic-ip
|
||||
|
||||
Tail logfiles of running instance with dns name:
|
||||
> ./aws-setup.sh --tail --public-dns-name ec2-x.compute.amazonaws.com
|
||||
|
||||
Create instance and use default values:
|
||||
> ./aws-setup.sh --instance --force
|
||||
|
||||
Create instance and use default values except instance name
|
||||
and instance short name:
|
||||
> ./aws-setup.sh --instance --instance-name Test --instance-short-name t --force
|
||||
|
||||
Associate instance with classic load balancer by
|
||||
passing all relevant parameters to script.
|
||||
Also use debug mode.
|
||||
> ./aws-setup.sh --region eu-west-2 --instance-type t2.medium
|
||||
--key-name leonradeck-keypair --key-file /cygdrive/c/Users/d069485/
|
||||
.ssh/leonradeck-keypair.pem --user-username test --user-password test
|
||||
--instance-name \"WC Santander 2017\" --instance-short-name test
|
||||
--new-admin-password admin -d --instance-with-load-balancer
|
||||
|
||||
|
||||
"
|
||||
}
|
||||
|
||||
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
|
||||
# --foo bar
|
||||
optstring=h
|
||||
unset options
|
||||
while (($#)); do
|
||||
case $1 in
|
||||
# If option is of type -ab
|
||||
-[!-]?*)
|
||||
# Loop over each character starting with the second
|
||||
for ((i=1; i < ${#1}; i++)); do
|
||||
c=${1:i:1}
|
||||
|
||||
# Add current char to options
|
||||
options+=("-$c")
|
||||
|
||||
# If option takes a required argument, and it's not the last char make
|
||||
# the rest of the string its argument
|
||||
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
|
||||
options+=("${1:i+1}")
|
||||
break
|
||||
fi
|
||||
done
|
||||
;;
|
||||
|
||||
# If option is of type --foo=bar
|
||||
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
|
||||
# add --endopts for --
|
||||
--) options+=(--endopts) ;;
|
||||
# Otherwise, nothing special
|
||||
*) options+=("$1") ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
set -- "${options[@]}"
|
||||
unset options
|
||||
|
||||
# Print help if no arguments were passed.
|
||||
# Uncomment to force arguments when invoking the script
|
||||
[[ $# -eq 0 ]] && set -- "--help"
|
||||
|
||||
# Set default value of variable without parameter value to false
|
||||
associate_clb=false
|
||||
associate_alb=false
|
||||
associate_elastic_ip=false
|
||||
instance=false
|
||||
tail=false
|
||||
|
||||
# Read the options and set variables
|
||||
while [[ $1 = -?* ]]; do
|
||||
case $1 in
|
||||
-h|--help) usage >&2; safeExit ;;
|
||||
--version) echo "$(basename $0) ${version}"; safeExit ;;
|
||||
-r|--region) shift; region_param=${1} ;;
|
||||
-t|--instance-type) shift; instance_type_param=${1} ;;
|
||||
-k|--key-name) shift; key_name_param=${1} ;;
|
||||
-i|--key-file) shift; key_file_param=${1} ;;
|
||||
-s|--ssh-user) shift; ssh_user_param=${1} ;;
|
||||
-u|--user-username) shift; user_username_param=${1} ;;
|
||||
-q|--user-password) shift; user_password_param=${1} ;;
|
||||
-n|--instance-name) shift; instance_name_param=${1} ;;
|
||||
-l|--instance-short-name) shift; instance_short_name_param=${1} ;;
|
||||
-a|--new-admin-password) shift; new_admin_password_param=${1} ;;
|
||||
-p|--public-dns-name) shift; public_dns_name_param=${1} ;;
|
||||
-f|--force) force=true ;;
|
||||
-d|--debug) debug=true ;;
|
||||
--associate-clb) associate_clb=true ;;
|
||||
--associate-alb) associate_alb=true ;;
|
||||
--associate-elastic-ip) associate_elastic_ip=true ;;
|
||||
--instance) instance=true ;;
|
||||
--tail) tail=true ;;
|
||||
--endopts) shift; break ;;
|
||||
*) die "invalid option: '$1'." ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Store the remaining part as arguments.
|
||||
args+=("$@")
|
||||
|
||||
############# RUN SCRIPT #############
|
||||
|
||||
# Trap bad exits with your cleanup function
|
||||
trap trapCleanup EXIT INT TERM
|
||||
|
||||
# Set IFS to preferred implementation
|
||||
IFS=$'\n\t'
|
||||
|
||||
# Exit on error. Append '||true' when you run the script if you expect an error.
|
||||
# set -o errexit
|
||||
|
||||
# Run in debug mode, if set
|
||||
if ${debug}; then set -x ; fi
|
||||
|
||||
# Exit on empty variable
|
||||
if ${strict}; then set -o nounset ; fi
|
||||
|
||||
# Bash will remember & return the highest exitcode in a chain of pipes.
|
||||
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
|
||||
set -o pipefail
|
||||
|
||||
# Invoke the checkDependenices function to test for Bash packages. Uncomment if needed.
|
||||
# checkDependencies
|
||||
|
||||
# Run script
|
||||
mainScript
|
||||
|
||||
# Exit cleanlyd
|
||||
safeExit
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Get latest release build from releases.sapsailing.com
|
||||
# @return latest build
|
||||
# -----------------------------------------------------------
|
||||
function get_latest_release(){
|
||||
|
||||
# get html from releases.sapsailing.com
|
||||
local html=$(wget releases.sapsailing.com -q -O -)
|
||||
|
||||
# extract all links
|
||||
local links=$(grep -Po '(?<=href=")[^"]*' <<< "$html")
|
||||
|
||||
# extract build strings (e.g. build-201709291756)
|
||||
local builds=$(grep -Po 'build-\d+' <<< "$links")
|
||||
|
||||
# sort build strings reverse using their date
|
||||
local result=$(sort -k1.1,1.8 -k1.9nr <<< "$builds")
|
||||
|
||||
# take latest build
|
||||
echo "$result" | head -1
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Get access token
|
||||
# @param $1 admin username
|
||||
# @param $2 admin password
|
||||
# @param $3 dns name of instance
|
||||
# @return access token
|
||||
# -----------------------------------------------------------
|
||||
function get_access_token(){
|
||||
local_echo "Getting access token..."
|
||||
curl_wrapper -X GET "http://$1:$2@$3:8888/security/api/restsecurity/access_token" | get_attribute '.access_token'
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Creates a new event with event name = instance name, no regatta and venuename="Default"
|
||||
# @param $1 access token of privileged user
|
||||
# @param $2 dns name of instance
|
||||
# @param $3 event name
|
||||
# @return event_id of created event
|
||||
# -----------------------------------------------------------
|
||||
function create_event(){
|
||||
local_echo "Creating event with name $3..."
|
||||
curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:8888/sailingserver/api/v1/events/createEvent" --data "eventName=$3" --data "venuename=Default" --data "createregatta=false" | get_attribute '.eventid'
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Changes password of user
|
||||
# @param $1 access token of privileged user
|
||||
# @param $2 dns name of instance
|
||||
# @param $3 admin username
|
||||
# @param $4 admin new password
|
||||
# -----------------------------------------------------------
|
||||
function change_admin_password(){
|
||||
local_echo "Changing password of user $3 to $4..."
|
||||
curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:8888/security/api/restsecurity/change_password" --data "username=$3" --data "password=$4"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Creates new user
|
||||
# @param $1 access token of privileged user
|
||||
# @param $2 dns name of instance
|
||||
# @param $3 user username
|
||||
# @param $4 user password
|
||||
# -----------------------------------------------------------
|
||||
function create_new_user(){
|
||||
local_echo "Creating new user \"$3\" with password \"$4\"..."
|
||||
curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:8888/security/api/restsecurity/create_user" --data "username=$3" --data "password=$4"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Wait until access token resource is available
|
||||
# @param $1 admin username
|
||||
# @param $2 admin password
|
||||
# @param $3 public_dns_name
|
||||
# -----------------------------------------------------------
|
||||
function wait_for_access_token_resource(){
|
||||
echo -n "Wait until resource \"/security/api/restsecurity/access_token\" is available..."
|
||||
curl_until_http_200 http://$1:$2@$3:8888/security/api/restsecurity/access_token
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Wait until create event resource is available
|
||||
# @param $1 public_dns_name
|
||||
# -----------------------------------------------------------
|
||||
function wait_for_create_event_resource(){
|
||||
echo -n 'Wait until resource "/sailingserver/api/v1/events/createEvent" is available...'
|
||||
curl_until_http_401 http://$1:8888/sailingserver/api/v1/events/createEvent
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Patch 001-events.conf with ssl macros
|
||||
# @param $1 dns name
|
||||
# @param $2 event id
|
||||
# @param $3 ssh user
|
||||
# @param $4 public dns name
|
||||
# @param $5 use ssl
|
||||
# -----------------------------------------------------------
|
||||
function configure_apache(){
|
||||
wait_for_ssh_connection "$3" "$4"
|
||||
wait_for_001_events_patch "$3" "$4"
|
||||
local patched_content="Use Event-SSL $1 \"$2\" 127.0.0.1 8888"
|
||||
ssh_wrapper $3@$4 echo "$patched_content >> $events_conf"
|
||||
#ssh_wraper $3@$4 "/etc/init.d/httpd reload"
|
||||
}
|
||||
|
||||
function wait_for_001_events_patch(){
|
||||
echo -n "Waiting for 001-events.conf to be created..."
|
||||
while [[ $(ssh_wrapper $1@$2 test -f $events_conf && echo "ok") != "ok" ]]; do
|
||||
echo -n .
|
||||
sleep 2
|
||||
done
|
||||
success "001-events.conf now exists. Appending macros to overwrite sailing script"
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Query for public dns name of instance
|
||||
# @param $1 instance id
|
||||
# @return public dns name
|
||||
# -----------------------------------------------------------
|
||||
function query_public_dns_name(){
|
||||
local_echo "Querying for the instance public dns name..."
|
||||
aws_wrapper --region "$region" ec2 describe-instances --instance-ids "$1" --output text --query 'Reservations[*].Instances[*].PublicDnsName'
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Query for default vpc id
|
||||
# @return default vpc id
|
||||
# -----------------------------------------------------------
|
||||
function get_default_vpc_id(){
|
||||
local_echo "Querying for the default vpc id..."
|
||||
aws_wrapper ec2 describe-vpcs --query 'Vpcs[?IsDefault==`true`].VpcId' --output text
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Allocates an elastic ip address
|
||||
# @return elastic ip
|
||||
# -----------------------------------------------------------
|
||||
function allocate_address(){
|
||||
local_echo "Allocating elastic ip..."
|
||||
aws_wrapper ec2 allocate-address | get_attribute '.PublicIp'
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Associate an elastic ip address with an instance
|
||||
# @param $1 instance_id
|
||||
# @param $2 elastic_ip
|
||||
# -----------------------------------------------------------
|
||||
function associate_address(){
|
||||
local_echo "Associating instance \"$1\" with elastic ip \"$2\"..."
|
||||
aws_wrapper ec2 associate-address --instance-id "$1" --public-ip "$2"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Get all availability zones of region
|
||||
# @return availability zones array
|
||||
# -----------------------------------------------------------
|
||||
function get_availability_zones(){
|
||||
aws_wrapper ec2 --region $region describe-availability-zones --query "AvailabilityZones[].ZoneName"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Wait until instance is recognized by AWS
|
||||
# @param $1 instance_id
|
||||
# -----------------------------------------------------------
|
||||
function wait_instance_exists(){
|
||||
local_echo "Wait until instance \"$1\" is recognized by AWS..."
|
||||
aws_wrapper ec2 wait instance-exists --instance-ids $1
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Wait until ssh connection is established
|
||||
# @param $1 ssh user
|
||||
# @param $2 dns name of instance
|
||||
# -----------------------------------------------------------
|
||||
function wait_for_ssh_connection(){
|
||||
echo -n "Connecting to $1@$2..."
|
||||
while ! ssh_wrapper -q $1@$2 true 1>&2; do
|
||||
echo -n .
|
||||
sleep 2
|
||||
done
|
||||
success "SSH Connection \"$1@$2\" is established."
|
||||
}
|
||||
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Creates $count instances inside $region with image $image_id and
|
||||
# $instance_type and $user_data as well as according $tag_specifications,
|
||||
# $security_group_ids and $key_name
|
||||
# @return instance id
|
||||
# -----------------------------------------------------------
|
||||
function run_instance(){
|
||||
local_echo "Creating instance..."
|
||||
write_user_data_to_file
|
||||
|
||||
local command="aws_wrapper --region $region ec2 run-instances"
|
||||
command+=$(add_param "region" $region)
|
||||
command+=$(add_param "image-id" $image_id)
|
||||
command+=$(add_param "count" $instance_count)
|
||||
command+=$(add_param "instance-type" $instance_type)
|
||||
command+=$(add_param "key-name" $key_name)
|
||||
command+=$(add_param "security-group-ids" $instance_security_group_ids)
|
||||
command+=$(add_param "user-data" 'file://${tmpDir}/$user_data_file')
|
||||
command+=$(add_param "tag-specifications" $(printf $tag_specifications "$instance_name"))
|
||||
|
||||
eval "$command"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Creates a file with user data for instance creation
|
||||
# -----------------------------------------------------------
|
||||
function write_user_data_to_file(){
|
||||
local MONGODB_HOST="$mongodb_host"
|
||||
local MONGODB_PORT="$mongodb_port"
|
||||
local MONGODB_NAME="$(lower_trim $instance_name | only_letters_and_numbers)"
|
||||
local REPLICATION_CHANNEL="$MONGODB_NAME"
|
||||
local SERVER_NAME="$MONGODB_NAME"
|
||||
local USE_ENVIRONMENT="live-server"
|
||||
local INSTALL_FROM_RELEASE="$(get_latest_release)"
|
||||
local SERVER_STARTUP_NOTIFY="$default_server_startup_notify"
|
||||
|
||||
local content=
|
||||
content+=$(add_user_data_variable "MONGODB_HOST" $MONGODB_HOST)
|
||||
content+=$(add_user_data_variable "MONGODB_PORT" $MONGODB_PORT)
|
||||
content+=$(add_user_data_variable "MONGODB_NAME" $MONGODB_NAME)
|
||||
content+=$(add_user_data_variable "INSTALL_FROM_RELEASE" $INSTALL_FROM_RELEASE)
|
||||
content+=$(add_user_data_variable "USE_ENVIRONMENT" $USE_ENVIRONMENT)
|
||||
content+=$(add_user_data_variable "REPLICATION_CHANNEL" $REPLICATION_CHANNEL)
|
||||
content+=$(add_user_data_variable "SERVER_NAME" $SERVER_NAME)
|
||||
content+=$(add_user_data_variable "SERVER_STARTUP_NOTIFY" $SERVER_STARTUP_NOTIFY)
|
||||
|
||||
echo "$content" > "${tmpDir}/$user_data_file"
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Create target group for load balancer
|
||||
# @param $1 target group name
|
||||
# @return json result
|
||||
# -----------------------------------------------------------
|
||||
function create_target_group(){
|
||||
local_echo "Creating target group..."
|
||||
local target_group_name=$(echo "$1" | only_letters_and_numbers)
|
||||
aws_wrapper elbv2 create-target-group --name $target_group_name --protocol HTTPS --port 443 --vpc-id "$2"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Register instances within target group
|
||||
# @param $1 target group arn
|
||||
# @param $2 instance id
|
||||
# -----------------------------------------------------------
|
||||
function register_targets(){
|
||||
local_echo "Register instance within target group..."
|
||||
aws elbv2 register-targets --target-group-arn "$1" --targets Id="$2"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Create rule for listener
|
||||
# @param $1 listener arn
|
||||
# @param $2 sub domain
|
||||
# @param $3 target group arn
|
||||
# @return json result
|
||||
# -----------------------------------------------------------
|
||||
function create_rule(){
|
||||
local_echo "Creating rule for listener..."
|
||||
local subdomain=$(echo "$2" | only_letters_and_numbers)
|
||||
local priority=$(($(get_rule_with_highest_priority $1) + 1))
|
||||
local domain="$subdomain.dummy.sapsailing.com"
|
||||
aws_wrapper elbv2 create-rule --listener-arn $1 --priority $priority --conditions Field=host-header,Values="$domain" --actions Type=forward,TargetGroupArn=$3 1>&2
|
||||
echo "$domain"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Get rule with highest priority out of listener
|
||||
# @param $1 listener arn
|
||||
# @return highest priority
|
||||
# -----------------------------------------------------------
|
||||
function get_rule_with_highest_priority(){
|
||||
local max_priority=$(aws_wrapper elbv2 describe-rules --listener-arn $1 --query "sort_by(Rules, &Priority)[*].{P: Priority} | [-2:-1]" --output text)
|
||||
if is_number $max_priority; then
|
||||
echo $max_priority
|
||||
else
|
||||
echo 1
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Create load balancer with HTTP rule
|
||||
# @param $1 load_balancer_name
|
||||
# @return json result
|
||||
# -----------------------------------------------------------
|
||||
function create_load_balancer_http(){
|
||||
local load_balancer_name=$(echo "$1" | only_letters_and_numbers)
|
||||
local_echo "Creating load balancer \"$load_balancer_name\"..."
|
||||
aws_wrapper elb create-load-balancer --load-balancer-name $load_balancer_name --listeners "Protocol=HTTP,LoadBalancerPort=80,InstanceProtocol=HTTP,InstancePort=80" --availability-zones "$(get_availability_zones)" --security-groups "$elb_security_group_ids"
|
||||
}
|
||||
|
||||
# NOT TESTED
|
||||
# -----------------------------------------------------------
|
||||
# Create load balancer with HTTPS rule
|
||||
# @param $1 load_balancer_name
|
||||
# @param $2 certificate arn
|
||||
# @param $2 security group ids
|
||||
# @return json result
|
||||
# -----------------------------------------------------------
|
||||
function create_load_balancer_https(){
|
||||
local load_balancer_name=$(echo "$1" | only_letters_and_numbers)
|
||||
local_echo "Creating load balancer \"$load_balancer_name\"..."
|
||||
aws_wrapper elb create-load-balancer --load-balancer-name $load_balancer_name --listeners "Protocol=HTTP,LoadBalancerPort=80,InstanceProtocol=HTTP,InstancePort=80" "Protocol=HTTPS,LoadBalancerPort=443,InstanceProtocol=HTTP,InstancePort=80,SSLCertificateId=$2" --availability-zones "$(get_availability_zones)" --security-groups "$elb_security_group_ids"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Configure health check HTTP
|
||||
# @param $1 load balancer name
|
||||
# @return json result
|
||||
# -----------------------------------------------------------
|
||||
function configure_health_check_http(){
|
||||
local load_balancer_name=$(echo "$1" | only_letters_and_numbers)
|
||||
local_echo "Configuring load balancer health check..."
|
||||
aws_wrapper elb configure-health-check --load-balancer-name "$load_balancer_name" --health-check Target=TCP:8888,Interval=15,UnhealthyThreshold=2,HealthyThreshold=3,Timeout=5
|
||||
}
|
||||
|
||||
# NOT TESTED
|
||||
# -----------------------------------------------------------
|
||||
# Configure health check HTTPS
|
||||
# @param $1 load balancer name
|
||||
# @return json result
|
||||
# -----------------------------------------------------------
|
||||
function configure_health_check_https(){
|
||||
local_echo "Configuring load balancer health check..."
|
||||
local load_balancer_name=$(echo "$1" | only_letters_and_numbers)
|
||||
aws_wrapper elb configure-health-check --load-balancer-name "$load_balancer_name" --health-check Target=HTTPS:443/index.html,Interval=15,UnhealthyThreshold=2,HealthyThreshold=3,Timeout=5
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Add instance to load balancer
|
||||
# @param $1 load balancer name
|
||||
# @param $2 instance id
|
||||
# @return json result
|
||||
# -----------------------------------------------------------
|
||||
function add_instance_to_elb(){
|
||||
local_echo "Adding instance to elb..."
|
||||
local load_balancer_name=$(echo "$1" | only_letters_and_numbers)
|
||||
aws_wrapper elb register-instances-with-load-balancer --load-balancer-name $load_balancer_name --instances $2
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# As long as variable is empty, prompt again.
|
||||
# @param $1 prompt message
|
||||
# @param $2 default value
|
||||
# @param $3 variable that is receiving the value
|
||||
# -----------------------------------------------------------
|
||||
function ask(){
|
||||
while [[ -z "${!3}" ]]
|
||||
do
|
||||
require_input "$1" "$2" $3
|
||||
done
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# If the needed variable was not passed as a parameter and is not
|
||||
# already set, then prompt the user to input a value and also
|
||||
# show a default value. If the variable was passed by using a parameter,
|
||||
# then set its value to the parameter value.
|
||||
# @param $1 parameter value
|
||||
# @param $2 variable
|
||||
# @param $3 default value
|
||||
# @param $4 prompt message
|
||||
# -----------------------------------------------------------
|
||||
function require_variable(){
|
||||
# if parameter is empty
|
||||
if [ -z "$1" ]; then
|
||||
# if required variable is empty
|
||||
if [ -z "${!2}" ]; then
|
||||
# if force is enabled
|
||||
if [ "$force" = true ]; then
|
||||
# read default value into required variable
|
||||
read -r "$2" <<< "$3"
|
||||
fi
|
||||
# if force not enabled, ask user for input
|
||||
ask "$4" "$3" $2
|
||||
fi
|
||||
else
|
||||
# if parameter is not empty, read its value into required variable
|
||||
read -r "$2" <<< "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Workaround: use stderr stream for console output
|
||||
# @param $1 message
|
||||
# -----------------------------------------------------------
|
||||
function local_echo(){
|
||||
echo "$1" >&2
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Prompt user for input and save value to variable
|
||||
# @param $1 prompt message
|
||||
# @param $2 default value
|
||||
# @param $3 variable that is receiving the value
|
||||
# -----------------------------------------------------------
|
||||
function require_input(){
|
||||
read -e -p "$1" -i "$2" $3
|
||||
}
|
||||
|
||||
function require_region(){
|
||||
require_variable "$region_param" region "$default_region" "$region_ask_message"
|
||||
}
|
||||
|
||||
function require_instance_type(){
|
||||
require_variable "$instance_type_param" instance_type "$default_instance_type" "$instance_type_ask_message"
|
||||
|
||||
}
|
||||
function require_instance_name(){
|
||||
require_variable "$instance_name_param" instance_name "$default_instance_name" "$instance_name_ask_message"
|
||||
}
|
||||
|
||||
function require_instance_short_name(){
|
||||
require_variable "$instance_short_name_param" instance_short_name "$default_instance_short_name" "$instance_short_name_ask_message"
|
||||
}
|
||||
|
||||
function require_key_name(){
|
||||
require_variable "$key_name_param" key_name "$default_key_name" "$key_name_ask_message"
|
||||
}
|
||||
|
||||
function require_key_file(){
|
||||
require_variable "$key_file_param" key_file "$default_key_file" "$key_file_ask_message"
|
||||
}
|
||||
|
||||
function require_new_admin_password(){
|
||||
require_variable "$new_admin_password_param" new_admin_password "$default_new_admin_password" "$new_admin_password_ask_message"
|
||||
}
|
||||
|
||||
function require_user_username(){
|
||||
require_variable "$user_username_param" user_username "$default_user_username" "$user_username_ask_message"
|
||||
}
|
||||
|
||||
function require_user_password(){
|
||||
require_variable "$user_password_param" user_password "$default_user_password" "$user_password_ask_message"
|
||||
}
|
||||
|
||||
function require_public_dns_name(){
|
||||
require_variable "$public_dns_name_param" public_dns_name "" "$public_dns_name_ask_message"
|
||||
}
|
||||
|
||||
function require_ssh_user(){
|
||||
require_variable "$ssh_user_param" ssh_user "" "$ssh_user_ask_message"
|
||||
}
|
||||
|
||||
region_ask_message="Please enter the region for the instance: "
|
||||
instance_type_ask_message="Please enter the instance type: "
|
||||
key_name_ask_message="Please enter the name of your keypair to connect to the instance: "
|
||||
instance_name_ask_message="Please enter a name for the instance: (e.g \"WC Santander 2017\"): "
|
||||
instance_short_name_ask_message="Please enter a short name for the instance (e.g. \"wcs17\"): "
|
||||
key_file_ask_message="Please enter the file path of the keypair or leave empty to use default ssh key: "
|
||||
new_admin_password_ask_message="Please enter a new password for the admin user: "
|
||||
mongo_db_host_ask_message="Please enter the ip adress of the mongo db server: "
|
||||
mongo_db_port_ask_message="Please enter the port of the mongo db server: "
|
||||
user_username_ask_message="Please enter the username of your new user: "
|
||||
user_password_ask_message="Please enter the password of your new user: "
|
||||
public_dns_name_ask_message="Please enter the public dns name: "
|
||||
ssh_user_ask_message="Please enter the ssh user: "
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# NOT TESTED
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Create CNAME record for the hosted zone to forward to a load balancer
|
||||
# @param $1 subdomain name
|
||||
# @param $2 TTL
|
||||
# @param $3 load balancer dns name
|
||||
# @return json result
|
||||
# -----------------------------------------------------------
|
||||
function route53_change_resource_record(){
|
||||
create_route53_record_file "$1" "$2" "$3"
|
||||
local_echo "Creating Route53 record set (Name: $1.sapsailing.com Value: $3 Type: CNAME)..."
|
||||
aws_wrapper route53 change-resource-record-sets --hosted-zone-id "$hosted_zone_id" --change-batch "file://${tmpDir}/$change_resource_record_set_file"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Creates a file which includes the record change as json
|
||||
# @param $1 subdomain name
|
||||
# @param $2 TTL
|
||||
# @param $3 load balancer dns name
|
||||
# -----------------------------------------------------------
|
||||
function create_route53_record_file(){
|
||||
local json=$(printf '{"Changes":[{"Action":"CREATE","ResourceRecordSet": {"Name": "%s.sapsailing.com","Type": "CNAME","TTL": %s,"ResourceRecords":[{"Value": "%s"}]}}]}' $1 $2 $3)
|
||||
echo "$json" > "${tmpDir}/$change_resource_record_set_file"
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Construct tmux user interface
|
||||
# -----------------------------------------------------------
|
||||
function construct_ui() {
|
||||
# enable scrolling, clicking on panes etc.
|
||||
tmux set -g mouse on
|
||||
|
||||
# enable named border at the top of panes
|
||||
tmux set -g pane-border-status top
|
||||
tmux set -g pane-border-format " [#{pane_index}] - #T "
|
||||
|
||||
# close all panes except one
|
||||
reset_panes
|
||||
sleep 1
|
||||
|
||||
# construct pane layout
|
||||
tmux split-window -h -p 50
|
||||
tmux select-pane -t 0
|
||||
tmux split-window -v -p 50
|
||||
tmux select-pane -t 2
|
||||
tmux split-window -v -p 50
|
||||
tmux select-pane -t 0
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Opens ssh connections on all three panes
|
||||
# @param $1 ssh user
|
||||
# @param $2 dns name of instance
|
||||
# -----------------------------------------------------------
|
||||
function open_connections() {
|
||||
|
||||
# Can't use ssh wrapper at this point because function cannot be export within tmux environment.
|
||||
if [ -z $key_file ]; then
|
||||
command="ssh -o StrictHostKeyChecking=no $1@$2"
|
||||
else
|
||||
command="ssh -o StrictHostKeyChecking=no -i $key_file $1@$2"
|
||||
fi
|
||||
|
||||
# Send $command to all three panes and execute
|
||||
tmux send-keys -t 1 $command C-m
|
||||
tmux send-keys -t 2 $command C-m
|
||||
tmux send-keys -t 3 $command C-m
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Close all panes except first
|
||||
# -----------------------------------------------------------
|
||||
function reset_panes(){
|
||||
if more_panes_are_open; then
|
||||
tmux kill-pane -a -t 0
|
||||
fi
|
||||
}
|
||||
|
||||
function get_number_of_panes(){
|
||||
tmux display-message -p '#{window_panes}'
|
||||
}
|
||||
|
||||
function more_panes_are_open(){
|
||||
if inside_tmux_session; then
|
||||
if [ "$(get_number_of_panes)" -gt 1 ]; then
|
||||
return 0;
|
||||
fi
|
||||
fi
|
||||
return 1;
|
||||
}
|
||||
|
||||
function check_if_tmux_is_used(){
|
||||
check_dependencies
|
||||
check_environment
|
||||
}
|
||||
|
||||
function check_dependencies() {
|
||||
if ! is_tmux_available; then
|
||||
echo "The package \"tmux\" is required to run the script with this option"
|
||||
safeExit
|
||||
fi
|
||||
}
|
||||
|
||||
function check_environment() {
|
||||
if ! inside_tmux_session; then
|
||||
echo "To tail instance log files, please run this script inside a tmux session. To do so enter \"tmux\" into the console and start the script from there."
|
||||
safeExit
|
||||
fi
|
||||
}
|
||||
|
||||
function is_tmux_available(){
|
||||
type tmux >/dev/null 2>/dev/null
|
||||
}
|
||||
|
||||
function inside_tmux_session(){
|
||||
[ "$TERM" = "screen" ] && [ -n "$TMUX" ]
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Prompts user if open panes should be closed
|
||||
# -----------------------------------------------------------
|
||||
function confirm_reset_panes(){
|
||||
if more_panes_are_open; then
|
||||
seek_confirmation "Do you want to close all open panes?"
|
||||
if is_confirmed; then
|
||||
reset_panes
|
||||
else
|
||||
safeExit
|
||||
fi
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Does the error checking for curl commands.
|
||||
# Captures response and http code of curl command into variable $out.
|
||||
# Checks if http code is 200.
|
||||
# If yes print success and return response.
|
||||
# If not then print error.
|
||||
# @return latest build
|
||||
# -----------------------------------------------------------
|
||||
function curl_wrapper(){
|
||||
local out;
|
||||
out=$(curl -qSfsw '\n%{http_code}' "$@")
|
||||
#local return_code=$?
|
||||
local command=$(echo "$@" | tr '\n' ' ')
|
||||
local status_code=$(get_status_code "$out")
|
||||
local response=$(get_response "$out")
|
||||
local message=$(get_http_code_message $status_code)
|
||||
|
||||
if is_http_ok $status_code; then
|
||||
success "[ OK ]"
|
||||
echo $response
|
||||
else
|
||||
error "[ ERROR ]"
|
||||
notice "Function (${FUNCNAME[1]}): [$status_code] $message\n\n$command"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Does the error checking for aws commands. It also remove all
|
||||
# carriage return symbols from response.
|
||||
# -----------------------------------------------------------
|
||||
function aws_wrapper(){
|
||||
local out;
|
||||
out=$(aws "$@")
|
||||
if command_was_successful $?; then
|
||||
success "[ OK ]"
|
||||
echo $out | sanitize
|
||||
else
|
||||
error "[ ERROR ]"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Automatically uses key file for ssh if possible.
|
||||
# -----------------------------------------------------------
|
||||
function ssh_wrapper(){
|
||||
if [ -z $key_file ]; then
|
||||
ssh -o StrictHostKeyChecking=no "$@"
|
||||
else
|
||||
ssh -o StrictHostKeyChecking=no -i $key_file "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Executes curl command until status code is 200
|
||||
# -----------------------------------------------------------
|
||||
function curl_until_http_200(){
|
||||
while [[ $(curl -s -o /dev/null -w ''%{http_code}'' --connect-timeout $http_retry_interval "$@") != "200" ]];
|
||||
do
|
||||
echo -n "."
|
||||
sleep $http_retry_interval;
|
||||
done
|
||||
echo ""
|
||||
success "[ OK ]"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Executes curl command until status code is 401.
|
||||
# TODO: Fix code duplication (curl_until_http_200, curl_until_http_401)
|
||||
# -----------------------------------------------------------
|
||||
function curl_until_http_401(){
|
||||
while [[ $(curl -s -o /dev/null -w ''%{http_code}'' --connect-timeout $http_retry_interval "$@") != "401" ]];
|
||||
do
|
||||
echo -n "."
|
||||
sleep $http_retry_interval;
|
||||
done
|
||||
echo ""
|
||||
success "[ OK ]"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Add instance to application load balancer
|
||||
# ------------------------------------------------------
|
||||
|
||||
function associate_alb_start(){
|
||||
associate_alb_require
|
||||
associate_alb_execute
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# All these variables are needed for this scenario
|
||||
# If one variable is not assigned or passed by parameter
|
||||
# the user will be prompted to enter a value
|
||||
# -----------------------------------------------------------
|
||||
function associate_alb_require(){
|
||||
require_region
|
||||
require_instance_type
|
||||
require_instance_name
|
||||
require_instance_short_name
|
||||
require_key_name
|
||||
require_key_file
|
||||
require_new_admin_password
|
||||
require_user_username
|
||||
require_user_password
|
||||
}
|
||||
|
||||
function associate_alb_execute() {
|
||||
local vpc_id=$(get_default_vpc_id)
|
||||
local target_group_arn=$(create_target_group $instance_name $vpc_id | get_attribute '.TargetGroups[0].TargetGroupArn')
|
||||
register_targets $target_group_arn $instance_id
|
||||
local domain=$(create_rule $listener_arn $instance_short_name $target_group_arn)
|
||||
|
||||
header "Apache configuration"
|
||||
configure_apache "$domain" "$event_id" "$ssh_user" "$public_dns_name" "ssl"
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Create classic load balancer for instance
|
||||
# ------------------------------------------------------
|
||||
|
||||
function associate_clb_start(){
|
||||
associate_clb_require
|
||||
associate_clb_execute
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# All these variables are needed for this scenario
|
||||
# If one variable is not assigned or passed by parameter
|
||||
# the user will be prompted to enter a value
|
||||
# -----------------------------------------------------------
|
||||
function associate_clb_require(){
|
||||
require_region
|
||||
require_instance_type
|
||||
require_instance_name
|
||||
require_instance_short_name
|
||||
require_key_name
|
||||
require_key_file
|
||||
require_new_admin_password
|
||||
require_user_username
|
||||
require_user_password
|
||||
}
|
||||
|
||||
function associate_clb_execute() {
|
||||
header "Associating classic load balancer"
|
||||
|
||||
local load_balancer_dns_name=$(create_load_balancer_https "$instance_name" "$certificate_arn" | get_attribute '.DNSName')
|
||||
local json_health_check=$(configure_health_check_https "$instance_name")
|
||||
local json_added_instance_response=$(add_instance_to_elb "$instance_name" "$instance_id")
|
||||
|
||||
header "Apache configuration"
|
||||
configure_apache "$public_dns_name" "$event_id" "$ssh_user" "$public_dns_name" "ssl"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Create elastic ip and associate it with instance
|
||||
# ------------------------------------------------------
|
||||
|
||||
function associate_elastic_ip_start(){
|
||||
associate_elastic_ip_require
|
||||
associate_elastic_ip_execute
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# All these variables are needed for this scenario
|
||||
# If one variable is not assigned or passed by parameter
|
||||
# the user will be prompted to enter a value
|
||||
# -----------------------------------------------------------
|
||||
function associate_elastic_ip_require(){
|
||||
require_region
|
||||
require_instance_type
|
||||
require_instance_name
|
||||
require_instance_short_name
|
||||
require_key_name
|
||||
require_key_file
|
||||
require_new_admin_password
|
||||
require_user_username
|
||||
require_user_password
|
||||
}
|
||||
|
||||
function associate_elastic_ip_execute() {
|
||||
header "Elastic ip creation"
|
||||
|
||||
local elastic_ip=$(allocate_address)
|
||||
associate_address "$instance_id" "$elastic_ip"
|
||||
|
||||
header "Apache configuration"
|
||||
configure_apache "$elastic_ip" "$event_id" "$ssh_user" "$elastic_ip" "ssl"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Scenario for creating an instance
|
||||
# ------------------------------------------------------
|
||||
|
||||
function instance_start(){
|
||||
instance_require
|
||||
instance_execute
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# All these variables are needed for this scenario
|
||||
# If one variable is not assigned or passed by parameter
|
||||
# the user will be prompted to enter a value
|
||||
# -----------------------------------------------------------
|
||||
function instance_require(){
|
||||
require_region
|
||||
require_instance_type
|
||||
require_instance_name
|
||||
require_instance_short_name
|
||||
require_key_name
|
||||
require_key_file
|
||||
require_new_admin_password
|
||||
require_user_username
|
||||
require_user_password
|
||||
}
|
||||
|
||||
function instance_execute() {
|
||||
header "Instance Initialization"
|
||||
|
||||
local json_instance=$(run_instance)
|
||||
instance_id=$(echo "$json_instance" | get_attribute '.Instances[0].InstanceId')
|
||||
instance_private_ip=$(echo "$json_instance" | get_attribute '.Instances[0].PrivateIpAddress')
|
||||
|
||||
wait_instance_exists "$instance_id"
|
||||
|
||||
public_dns_name=$(query_public_dns_name "$instance_id")
|
||||
|
||||
header "SSH Connection"
|
||||
|
||||
wait_for_ssh_connection "$ssh_user" "$public_dns_name"
|
||||
|
||||
if $tail; then
|
||||
tail_start
|
||||
fi
|
||||
|
||||
header "Event and user creation"
|
||||
|
||||
wait_for_access_token_resource "$admin_username" "$admin_password" "$public_dns_name"
|
||||
access_token=$(get_access_token "$admin_username" "$admin_password" "$public_dns_name")
|
||||
|
||||
wait_for_create_event_resource "$public_dns_name"
|
||||
event_id=$(create_event "$access_token" "$public_dns_name" "$instance_name")
|
||||
|
||||
change_admin_password "$access_token" "$public_dns_name" "$admin_username" "$new_admin_password"
|
||||
create_new_user "$access_token" "$public_dns_name" "$user_username" "$user_password"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function tail_start(){
|
||||
tail_precondition
|
||||
tail_user_input
|
||||
tail_execute
|
||||
}
|
||||
|
||||
function tail_precondition(){
|
||||
check_if_tmux_is_used
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# All these variables are needed for this scenario
|
||||
# If one variable is not assigned or passed by parameter
|
||||
# the user will be prompted to enter a value
|
||||
# -----------------------------------------------------------
|
||||
function tail_user_input(){
|
||||
require_key_file
|
||||
require_ssh_user
|
||||
require_public_dns_name
|
||||
}
|
||||
|
||||
function tail_execute() {
|
||||
echo "Open tmux panes and start tailing log files..."
|
||||
construct_ui
|
||||
wait_for_ssh_connection "$ssh_user" "$public_dns_name"
|
||||
open_connections "$ssh_user" "$public_dns_name"
|
||||
tail_logfiles
|
||||
}
|
||||
|
||||
function tail_logfiles(){
|
||||
tmux send-keys -t 1 "clear;echo \"Waiting for file $sailing_0 to appear...\";touch $sailing_0;cat $sailing_0;tail -F -v $sailing_0" C-m
|
||||
tmux send-keys -t 2 "clear;cat $sailing_out;tail -F -v $sailing_out" C-m
|
||||
tmux send-keys -t 3 "clear;cat $sailing_err;tail -F -v $sailing_err" C-m
|
||||
}
|
||||
@@ -0,0 +1,738 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Creates a parameter from a key and value
|
||||
# @param $1 key
|
||||
# @param $2 value
|
||||
# @return result ("--key value")
|
||||
# -----------------------------------------------------------
|
||||
function add_param() {
|
||||
if [ ! -z "$2" ]; then
|
||||
local result=" --$1 $2"
|
||||
fi
|
||||
echo "$result"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Constructs string of user data variable name and value plus linebreak
|
||||
# @param $1 user data variable name
|
||||
# @param $2 user data variable value
|
||||
# @return name=value (linebreak) if value not empty else nothing
|
||||
# -----------------------------------------------------------
|
||||
function add_user_data_variable(){
|
||||
if ! [ -z "$2" ]; then
|
||||
local CR_LF=$'\r'$'\n'
|
||||
local content="$1=$2"
|
||||
content+=$CR_LF
|
||||
echo "$content"
|
||||
fi
|
||||
}
|
||||
|
||||
function command_was_successful(){
|
||||
[ $1 -eq 0 ]
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Checks if variable is a number
|
||||
# @param $1 returnvariablevalue
|
||||
# @return 0 if variable is a number
|
||||
# -----------------------------------------------------------
|
||||
function is_number(){
|
||||
[[ $1 =~ ^-?[0-9]+$ ]]
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Check if variable is a number and its value is 200
|
||||
# @param $1 variable
|
||||
# @return 0 if value is 200
|
||||
# -----------------------------------------------------------
|
||||
function is_http_ok(){
|
||||
is_number $1 && [ $1 == 200 ]
|
||||
}
|
||||
|
||||
function get_response(){
|
||||
echo "$1" | head -n-1
|
||||
}
|
||||
|
||||
function get_status_code(){
|
||||
echo "$1" | tail -n1
|
||||
}
|
||||
|
||||
function get_attribute(){
|
||||
jq -r $1 | sanitize
|
||||
}
|
||||
|
||||
function sanitize(){
|
||||
tr -d '\r'
|
||||
}
|
||||
|
||||
# ------------------------------------------------------
|
||||
# The following functions were part of a bash template
|
||||
# and could be useful in the future
|
||||
# ------------------------------------------------------
|
||||
|
||||
# Traps
|
||||
# ------------------------------------------------------
|
||||
# These functions are for use with different trap scenarios
|
||||
# ------------------------------------------------------
|
||||
|
||||
# Non destructive exit for when script exits naturally.
|
||||
# Usage: Add this function at the end of every script
|
||||
function safeExit() {
|
||||
# Delete temp files, if any
|
||||
if is_dir "${tmpDir}"; then
|
||||
rm -r "${tmpDir}"
|
||||
fi
|
||||
kill -WINCH $$
|
||||
trap - INT TERM EXIT
|
||||
exit
|
||||
}
|
||||
|
||||
# readFile
|
||||
# ------------------------------------------------------
|
||||
# Function to read a line from a file.
|
||||
#
|
||||
# Most often used to read the config files saved in my etc directory.
|
||||
# Outputs each line in a variable named $result
|
||||
# ------------------------------------------------------
|
||||
function readFile() {
|
||||
unset "${result}"
|
||||
while read result
|
||||
do
|
||||
echo "${result}"
|
||||
done < "$1"
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Escape a string
|
||||
# ------------------------------------------------------
|
||||
# usage: var=$(escape "String")
|
||||
# ------------------------------------------------------
|
||||
escape() { echo "${@}" | sed 's/[]\.|$(){}?+*^]/\\&/g'; }
|
||||
|
||||
# needSudo
|
||||
# ------------------------------------------------------
|
||||
# If a script needs sudo access, call this function which
|
||||
# requests sudo access and then keeps it alive.
|
||||
# ------------------------------------------------------
|
||||
function needSudo() {
|
||||
# Update existing sudo time stamp if set, otherwise do nothing.
|
||||
sudo -v
|
||||
while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null &
|
||||
}
|
||||
|
||||
# convertsecs
|
||||
# ------------------------------------------------------
|
||||
# Convert Seconds to human readable time
|
||||
#
|
||||
# To use this, pass a number (seconds) into the function as this:
|
||||
# print "$(convertsecs $TOTALTIME)"
|
||||
#
|
||||
# To compute the time it takes a script to run use tag the start and end times with
|
||||
# STARTTIME=$(date +"%s")
|
||||
# ENDTIME=$(date +"%s")
|
||||
# TOTALTIME=$(($ENDTIME-$STARTTIME))
|
||||
# ------------------------------------------------------
|
||||
function convertsecs() {
|
||||
((h=${1}/3600))
|
||||
((m=(${1}%3600)/60))
|
||||
((s=${1}%60))
|
||||
printf "%02d:%02d:%02d\n" $h $m $s
|
||||
}
|
||||
|
||||
# Join
|
||||
# ----------------------------------------------
|
||||
# This function joins items together with a user specified separator
|
||||
# Taken whole cloth from: http://stackoverflow.com/questions/1527049/bash-join-elements-of-an-array
|
||||
#
|
||||
# Usage:
|
||||
# join , a "b c" d #a,b c,d
|
||||
# join / var local tmp #var/local/tmp
|
||||
# join , "${FOO[@]}" #a,b,c
|
||||
# ----------------------------------------------
|
||||
function join() { local IFS="${1}"; shift; echo "${*}"; }
|
||||
|
||||
# File Checks
|
||||
# ------------------------------------------------------
|
||||
# A series of functions which make checks against the filesystem. For
|
||||
# use in if/then statements.
|
||||
#
|
||||
# Usage:
|
||||
# if is_file "file"; then
|
||||
# ...
|
||||
# fi
|
||||
# ------------------------------------------------------
|
||||
|
||||
function is_exists() {
|
||||
if [[ -e "$1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function is_not_exists() {
|
||||
if [[ ! -e "$1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function is_file() {
|
||||
if [[ -f "$1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function is_not_file() {
|
||||
if [[ ! -f "$1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function is_dir() {
|
||||
if [[ -d "$1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function is_not_dir() {
|
||||
if [[ ! -d "$1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function is_symlink() {
|
||||
if [[ -L "$1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function is_not_symlink() {
|
||||
if [[ ! -L "$1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function is_empty() {
|
||||
if [[ -z "$1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function is_not_empty() {
|
||||
if [[ -n "$1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Test whether a command exists
|
||||
# ------------------------------------------------------
|
||||
# Usage:
|
||||
# if type_exists 'git'; then
|
||||
# some action
|
||||
# else
|
||||
# some other action
|
||||
# fi
|
||||
# ------------------------------------------------------
|
||||
|
||||
function type_exists() {
|
||||
if [ "$(type -P "$1")" ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function type_not_exists() {
|
||||
if [ ! "$(type -P "$1")" ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Test which OS the user runs
|
||||
# $1 = OS to test
|
||||
# Usage: if is_os 'darwin'; then
|
||||
|
||||
function is_os() {
|
||||
if [[ "${OSTYPE}" == $1* ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
# SEEKING CONFIRMATION
|
||||
# ------------------------------------------------------
|
||||
# Asks questions of a user and then does something with the answer.
|
||||
# y/n are the only possible answers.
|
||||
#
|
||||
# USAGE:
|
||||
# seek_confirmation "Ask a question"
|
||||
# if is_confirmed; then
|
||||
# some action
|
||||
# else
|
||||
# some other action
|
||||
# fi
|
||||
#
|
||||
# Credt: https://github.com/kevva/dotfiles
|
||||
# ------------------------------------------------------
|
||||
|
||||
# Ask the question
|
||||
function seek_confirmation() {
|
||||
# echo ""
|
||||
input "$@"
|
||||
read -p " (y/n) " -n 1
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Test whether the result of an 'ask' is a confirmation
|
||||
function is_confirmed() {
|
||||
if "${force}"; then
|
||||
return 0
|
||||
else
|
||||
if [[ "${REPLY}" =~ ^[Yy]$ ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function is_not_confirmed() {
|
||||
if "${force}"; then
|
||||
return 1
|
||||
else
|
||||
if [[ "${REPLY}" =~ ^[Nn]$ ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Skip something
|
||||
# ------------------------------------------------------
|
||||
# Offer the user a chance to skip something.
|
||||
# Credit: https://github.com/cowboy/dotfiles
|
||||
# ------------------------------------------------------
|
||||
function skip() {
|
||||
REPLY=noskip
|
||||
read -t 5 -n 1 -s -p "${bold}To skip, press ${underline}X${reset}${bold} within 5 seconds.${reset}"
|
||||
if [[ "$REPLY" =~ ^[Xx]$ ]]; then
|
||||
notice " Skipping!"
|
||||
return 0
|
||||
else
|
||||
notice " Continuing..."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# help
|
||||
# ------------------------------------------------------
|
||||
# Prints help for a script when invoked from the command
|
||||
# line. Typically via '-h'. If additional flags or help
|
||||
# text is available in the script they will be printed
|
||||
# in the '$usage' variable.
|
||||
# ------------------------------------------------------
|
||||
function help () {
|
||||
echo "" 1>&2
|
||||
input " $@" 1>&2
|
||||
if [ -n "${usage}" ]; then # print usage information if available
|
||||
echo " ${usage}" 1>&2
|
||||
fi
|
||||
echo "" 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
function pauseScript() {
|
||||
# A simple function used to pause a script at any point and
|
||||
# only continue on user input
|
||||
seek_confirmation "Ready to continue?"
|
||||
if is_confirmed; then
|
||||
info "Continuing"
|
||||
else
|
||||
warning "Exiting Script."
|
||||
safeExit
|
||||
fi
|
||||
}
|
||||
|
||||
function in_array() {
|
||||
# Determine if a value is in an array.
|
||||
# Usage: if in_array "VALUE" "${ARRAY[@]}"; then ...
|
||||
local value="$1"; shift
|
||||
for arrayItem in "$@"; do
|
||||
[[ "${arrayItem}" == "${value}" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Text Transformations
|
||||
# -----------------------------------
|
||||
# Transform text using these functions.
|
||||
# Adapted from https://github.com/jmcantrell/bashful
|
||||
# -----------------------------------
|
||||
|
||||
lower() {
|
||||
# Convert stdin to lowercase.
|
||||
# usage: text=$(lower <<<"$1")
|
||||
# echo "MAKETHISLOWERCASE" | lower
|
||||
tr '[:upper:]' '[:lower:]'
|
||||
}
|
||||
|
||||
upper() {
|
||||
# Convert stdin to uppercase.
|
||||
# usage: text=$(upper <<<"$1")
|
||||
# echo "MAKETHISUPPERCASE" | upper
|
||||
tr '[:lower:]' '[:upper:]'
|
||||
}
|
||||
|
||||
function lower_trim(){
|
||||
echo $1 | lower | trim
|
||||
}
|
||||
|
||||
# Removes all whitespaces
|
||||
trim() {
|
||||
tr -d '[:space:]'
|
||||
}
|
||||
|
||||
ltrim() {
|
||||
# Removes all leading whitespace (from the left).
|
||||
local char=${1:-[:space:]}
|
||||
sed "s%^[${char//%/\\%}]*%%"
|
||||
}
|
||||
|
||||
rtrim() {
|
||||
# Removes all trailing whitespace (from the right).
|
||||
local char=${1:-[:space:]}
|
||||
sed "s%[${char//%/\\%}]*$%%"
|
||||
}
|
||||
|
||||
lttrim() {
|
||||
# Removes all leading/trailing whitespace
|
||||
# Usage examples:
|
||||
# echo " foo bar baz " | trim #==> "foo bar baz"
|
||||
ltrim "$1" | rtrim "$1"
|
||||
}
|
||||
|
||||
only_letters_and_numbers(){
|
||||
tr -d -c '[:alnum:]'
|
||||
}
|
||||
|
||||
squeeze() {
|
||||
# Removes leading/trailing whitespace and condenses all other consecutive
|
||||
# whitespace into a single space.
|
||||
#
|
||||
# Usage examples:
|
||||
# echo " foo bar baz " | squeeze #==> "foo bar baz"
|
||||
|
||||
local char=${1:-[[:space:]]}
|
||||
sed "s%\(${char//%/\\%}\)\+%\1%g" | trim "$char"
|
||||
}
|
||||
|
||||
squeeze_lines() {
|
||||
# <doc:squeeze_lines> {{{
|
||||
#
|
||||
# Removes all leading/trailing blank lines and condenses all other
|
||||
# consecutive blank lines into a single blank line.
|
||||
#
|
||||
# </doc:squeeze_lines> }}}
|
||||
|
||||
sed '/^[[:space:]]\+$/s/.*//g' | cat -s | trim_lines
|
||||
}
|
||||
|
||||
progressBar() {
|
||||
# progressBar
|
||||
# -----------------------------------
|
||||
# Prints a progress bar within a for/while loop.
|
||||
# To use this function you must pass the total number of
|
||||
# times the loop will run to the function.
|
||||
#
|
||||
# usage:
|
||||
# for number in $(seq 0 100); do
|
||||
# sleep 1
|
||||
# progressBar 100
|
||||
# done
|
||||
# -----------------------------------
|
||||
if [[ "${quiet}" = "true" ]] || [ "${quiet}" == "1" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
local width
|
||||
width=30
|
||||
bar_char="#"
|
||||
|
||||
# Don't run this function when scripts are running in verbose mode
|
||||
if ${verbose}; then return; fi
|
||||
|
||||
# Reset the count
|
||||
if [ -z "${progressBarProgress}" ]; then
|
||||
progressBarProgress=0
|
||||
fi
|
||||
|
||||
# Do nothing if the output is not a terminal
|
||||
if [ ! -t 1 ]; then
|
||||
echo "Output is not a terminal" 1>&2
|
||||
return
|
||||
fi
|
||||
# Hide the cursor
|
||||
tput civis
|
||||
trap 'tput cnorm; exit 1' SIGINT
|
||||
|
||||
if [ ! "${progressBarProgress}" -eq $(( $1 - 1 )) ]; then
|
||||
# Compute the percentage.
|
||||
perc=$(( progressBarProgress * 100 / $1 ))
|
||||
# Compute the number of blocks to represent the percentage.
|
||||
num=$(( progressBarProgress * width / $1 ))
|
||||
# Create the progress bar string.
|
||||
bar=
|
||||
if [ ${num} -gt 0 ]; then
|
||||
bar=$(printf "%0.s${bar_char}" $(seq 1 ${num}))
|
||||
fi
|
||||
# Print the progress bar.
|
||||
progressBarLine=$(printf "%s [%-${width}s] (%d%%)" "Running Process" "${bar}" "${perc}")
|
||||
echo -en "${progressBarLine}\r"
|
||||
progressBarProgress=$(( progressBarProgress + 1 ))
|
||||
else
|
||||
# Clear the progress bar when complete
|
||||
echo -ne "${width}%\033[0K\r"
|
||||
unset progressBarProgress
|
||||
fi
|
||||
|
||||
tput cnorm
|
||||
}
|
||||
|
||||
htmlDecode() {
|
||||
# Decode HTML characters with sed
|
||||
# Usage: htmlDecode <string>
|
||||
echo "${1}" | sed -f "${SOURCEPATH}/htmlDecode.sed"
|
||||
}
|
||||
|
||||
htmlEncode() {
|
||||
# Encode HTML characters with sed
|
||||
# Usage: htmlEncode <string>
|
||||
echo "${1}" | sed -f "${SOURCEPATH}/htmlEncode.sed"
|
||||
}
|
||||
|
||||
urlencode() {
|
||||
# URL encoding/decoding from: https://gist.github.com/cdown/1163649
|
||||
# Usage: urlencode <string>
|
||||
|
||||
local length="${#1}"
|
||||
for (( i = 0; i < length; i++ )); do
|
||||
local c="${1:i:1}"
|
||||
case $c in
|
||||
[a-zA-Z0-9.~_-]) printf "%s" "$c" ;;
|
||||
*) printf '%%%02X' "'$c"
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
urldecode() {
|
||||
# Usage: urldecode <string>
|
||||
|
||||
local url_encoded="${1//+/ }"
|
||||
printf '%b' "${url_encoded//%/\x}"
|
||||
}
|
||||
|
||||
parse_yaml() {
|
||||
# Function to parse YAML files and add values to variables. Send it to a temp file and source it
|
||||
# https://gist.github.com/DinoChiesa/3e3c3866b51290f31243 which is derived from
|
||||
# https://gist.github.com/epiloque/8cf512c6d64641bde388
|
||||
#
|
||||
# Usage:
|
||||
# $ parse_yaml sample.yml > /some/tempfile
|
||||
#
|
||||
# parse_yaml accepts a prefix argument so that imported settings all have a common prefix
|
||||
# (which will reduce the risk of name-space collisions).
|
||||
#
|
||||
# $ parse_yaml sample.yml "CONF_"
|
||||
|
||||
local prefix=$2
|
||||
local s
|
||||
local w
|
||||
local fs
|
||||
s='[[:space:]]*'
|
||||
w='[a-zA-Z0-9_]*'
|
||||
fs="$(echo @|tr @ '\034')"
|
||||
sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
|
||||
-e "s|^\($s\)\($w\)$s[:-]$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$1" |
|
||||
awk -F"$fs" '{
|
||||
indent = length($1)/2;
|
||||
if (length($2) == 0) { conj[indent]="+";} else {conj[indent]="";}
|
||||
vname[indent] = $2;
|
||||
for (i in vname) {if (i > indent) {delete vname[i]}}
|
||||
if (length($3) > 0) {
|
||||
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
|
||||
printf("%s%s%s%s=(\"%s\")\n", "'"$prefix"'",vn, $2, conj[indent-1],$3);
|
||||
}
|
||||
}' | sed 's/_=/+=/g'
|
||||
}
|
||||
|
||||
httpStatus() {
|
||||
# -----------------------------------
|
||||
# Shamelessly taken from: https://gist.github.com/rsvp/1171304
|
||||
#
|
||||
# Usage: httpStatus URL [timeout] [--code or --status] [see 4.]
|
||||
# ^message with code (default)
|
||||
# ^code (numeric only)
|
||||
# ^in secs (default: 3)
|
||||
# ^URL without "http://" prefix works fine.
|
||||
#
|
||||
# 4. curl options: e.g. use -L to follow redirects.
|
||||
#
|
||||
# Dependencies: curl
|
||||
#
|
||||
# Example: $ httpStatus bit.ly
|
||||
# 301 Redirection: Moved Permanently
|
||||
#
|
||||
# Example: $ httpStatus www.google.com 100 -c
|
||||
# 200
|
||||
#
|
||||
# -----------------------------------
|
||||
local curlops
|
||||
local arg4
|
||||
local arg5
|
||||
local arg6
|
||||
local arg7
|
||||
local flag
|
||||
local timeout
|
||||
local url
|
||||
|
||||
saveIFS=${IFS}
|
||||
IFS=$' \n\t'
|
||||
|
||||
url=${1}
|
||||
timeout=${2:-'3'}
|
||||
# ^in seconds
|
||||
flag=${3:-'--status'}
|
||||
# curl options, e.g. -L to follow redirects
|
||||
arg4=${4:-''}
|
||||
arg5=${5:-''}
|
||||
arg6=${6:-''}
|
||||
arg7=${7:-''}
|
||||
curlops="${arg4} ${arg5} ${arg6} ${arg7}"
|
||||
|
||||
# __________ get the CODE which is numeric:
|
||||
code=`echo $(curl --write-out %{http_code} --silent --connect-timeout ${timeout} \
|
||||
--no-keepalive ${curlops} --output /dev/null ${url})`
|
||||
|
||||
# __________ get the STATUS (from code) which is human interpretable:
|
||||
|
||||
status=$(get_http_code_message $code)
|
||||
|
||||
# _______________ MAIN
|
||||
case ${flag} in
|
||||
--status) echo "${code} ${status}" ;;
|
||||
-s) echo "${code} ${status}" ;;
|
||||
--code) echo "${code}" ;;
|
||||
-c) echo "${code}" ;;
|
||||
*) echo " !! httpstatus: bad flag" && safeExit;;
|
||||
esac
|
||||
|
||||
IFS="${saveIFS}"
|
||||
}
|
||||
|
||||
function get_http_code_message(){
|
||||
case $1 in
|
||||
000) status="Not responding within timeout" ;;
|
||||
100) status="Informational: Continue" ;;
|
||||
101) status="Informational: Switching Protocols" ;;
|
||||
200) status="Successful: OK within timeout" ;;
|
||||
201) status="Successful: Created" ;;
|
||||
202) status="Successful: Accepted" ;;
|
||||
203) status="Successful: Non-Authoritative Information" ;;
|
||||
204) status="Successful: No Content" ;;
|
||||
205) status="Successful: Reset Content" ;;
|
||||
206) status="Successful: Partial Content" ;;
|
||||
300) status="Redirection: Multiple Choices" ;;
|
||||
301) status="Redirection: Moved Permanently" ;;
|
||||
302) status="Redirection: Found residing temporarily under different URI" ;;
|
||||
303) status="Redirection: See Other" ;;
|
||||
304) status="Redirection: Not Modified" ;;
|
||||
305) status="Redirection: Use Proxy" ;;
|
||||
306) status="Redirection: status not defined" ;;
|
||||
307) status="Redirection: Temporary Redirect" ;;
|
||||
400) status="Client Error: Bad Request" ;;
|
||||
401) status="Client Error: Unauthorized" ;;
|
||||
402) status="Client Error: Payment Required" ;;
|
||||
403) status="Client Error: Forbidden" ;;
|
||||
404) status="Client Error: Not Found" ;;
|
||||
405) status="Client Error: Method Not Allowed" ;;
|
||||
406) status="Client Error: Not Acceptable" ;;
|
||||
407) status="Client Error: Proxy Authentication Required" ;;
|
||||
408) status="Client Error: Request Timeout within timeout" ;;
|
||||
409) status="Client Error: Conflict" ;;
|
||||
410) status="Client Error: Gone" ;;
|
||||
411) status="Client Error: Length Required" ;;
|
||||
412) status="Client Error: Precondition Failed" ;;
|
||||
413) status="Client Error: Request Entity Too Large" ;;
|
||||
414) status="Client Error: Request-URI Too Long" ;;
|
||||
415) status="Client Error: Unsupported Media Type" ;;
|
||||
416) status="Client Error: Requested Range Not Satisfiable" ;;
|
||||
417) status="Client Error: Expectation Failed" ;;
|
||||
500) status="Server Error: Internal Server Error" ;;
|
||||
501) status="Server Error: Not Implemented" ;;
|
||||
502) status="Server Error: Bad Gateway" ;;
|
||||
503) status="Server Error: Service Unavailable" ;;
|
||||
504) status="Server Error: Gateway Timeout within timeout" ;;
|
||||
505) status="Server Error: HTTP Version Not Supported" ;;
|
||||
*) status="status not defined." ;;
|
||||
esac
|
||||
echo "$status"
|
||||
}
|
||||
|
||||
|
||||
function makeCSV() {
|
||||
# Creates a new CSV file if one does not already exist.
|
||||
# Takes passed arguments and writes them as a header line to the CSV
|
||||
# Usage 'makeCSV column1 column2 column3'
|
||||
|
||||
# Set the location and name of the CSV File
|
||||
if [ -z "${csvLocation}" ]; then
|
||||
csvLocation="${HOME}/Desktop"
|
||||
fi
|
||||
if [ -z "${csvName}" ]; then
|
||||
csvName="$(LC_ALL=C date +%Y-%m-%d)-${FUNCNAME[1]}.csv"
|
||||
fi
|
||||
csvFile="${csvLocation}/${csvName}"
|
||||
|
||||
# Overwrite existing file? If not overwritten, new content is added
|
||||
# to the bottom of the existing file
|
||||
if [ -f "${csvFile}" ]; then
|
||||
seek_confirmation "${csvFile} already exists. Overwrite?"
|
||||
if is_confirmed; then
|
||||
rm "${csvFile}"
|
||||
writeCSV "$@"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function writeCSV() {
|
||||
# Takes passed arguments and writes them as a comma separated line
|
||||
# Usage 'writeCSV column1 column2 column3'
|
||||
|
||||
csvInput=($@)
|
||||
saveIFS=$IFS
|
||||
IFS=','
|
||||
echo "${csvInput[*]}" >> "${csvFile}"
|
||||
IFS=$saveIFS
|
||||
|
||||
}
|
||||
|
||||
function json2yaml() {
|
||||
# convert json files to yaml using python and PyYAML
|
||||
python -c 'import sys, yaml, json; yaml.safe_dump(json.load(sys.stdin), sys.stdout, default_flow_style=False)' < "$1"
|
||||
}
|
||||
|
||||
function yaml2json() {
|
||||
# convert yaml files to json using python and PyYAML
|
||||
python -c 'import sys, yaml, json; json.dump(yaml.load(sys.stdin), sys.stdout, indent=4)' < "$1"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ------------------------------------------------------
|
||||
# The following variables were part of a bash template
|
||||
# and could be useful in the future
|
||||
# ------------------------------------------------------
|
||||
|
||||
# SCRIPTNAME
|
||||
scriptName=`basename $0` #Set Script Name variable
|
||||
scriptBasename="$(basename ${scriptName} .sh)" # Strips '.sh' from scriptName
|
||||
|
||||
# TIMESTAMPS
|
||||
|
||||
now=$(LC_ALL=C date +"%Y-%m-%d %H:%M:%S") # Returns: 2015-06-15 22:34:40
|
||||
datestamp=$(LC_ALL=C date +%Y-%m-%d) # Returns: 2015-06-14
|
||||
hourstamp=$(LC_ALL=C date +%r) # Returns: 10:34:40 PM
|
||||
timestamp=$(LC_ALL=C date +%Y%m%d_%H%M%S) # Returns: 20150614_223440
|
||||
today=$(LC_ALL=C date +"%m-%d-%Y") # Returns: 06-14-2015
|
||||
longdate=$(LC_ALL=C date +"%a, %d %b %Y %H:%M:%S %z") # Returns: Sun, 10 Jan 2016 20:47:53 -0500
|
||||
gmtdate=$(LC_ALL=C date -u -R | sed 's/\+0000/GMT/') # Returns: Wed, 13 Jan 2016 15:55:29 GMT
|
||||
|
||||
# THISHOST
|
||||
thisHost=$(hostname)
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Logging and Colors
|
||||
# ------------------------------------------------------
|
||||
# Here we set the colors for our script feedback.
|
||||
# Example usage: success "sometext"
|
||||
#------------------------------------------------------
|
||||
|
||||
# Set Colors
|
||||
bold=$(tput bold)
|
||||
underline=$(tput sgr 0 1)
|
||||
reset=$(tput sgr0)
|
||||
purple='\033[0;35m'
|
||||
red='\033[0;31m'
|
||||
green='\033[0;32m'
|
||||
tan=$(tput setaf 3)
|
||||
blue='\033[0;36m'
|
||||
|
||||
function _alert() {
|
||||
if [ "${1}" = "emergency" ]; then
|
||||
local color="${bold}${red}"
|
||||
fi
|
||||
if [ "${1}" = "error" ]; then local color="${bold}${red}"; fi
|
||||
if [ "${1}" = "warning" ]; then local color="${red}"; fi
|
||||
if [ "${1}" = "success" ]; then local color="${green}"; fi
|
||||
if [ "${1}" = "debug" ]; then local color="${purple}"; fi
|
||||
if [ "${1}" = "header" ]; then local color="${bold}""${tan}"; fi
|
||||
if [ "${1}" = "input" ]; then local color="${bold}"; printLog="false"; fi
|
||||
if [ "${1}" = "info" ] || [ "${1}" = "notice" ]; then local color=""; fi
|
||||
|
||||
# Don't use colors on pipes or non-recognized terminals
|
||||
# if [[ "${TERM}" != "xterm"* ]] || [ -t 1 ]; then color=""; reset=""; fi
|
||||
|
||||
# Print to $logFile
|
||||
if [[ ${printLog} = "true" ]] || [ "${printLog}" == "1" ]; then
|
||||
echo -e "$(date +"%m-%d-%Y %r") $(printf "[%9s]" "${1}") ${_message}" >> "${logFile}";
|
||||
fi
|
||||
|
||||
# Print to console when script is not 'quiet'
|
||||
if [[ "${quiet}" = "true" ]] || [ "${quiet}" == "1" ]; then
|
||||
return
|
||||
else
|
||||
echo -e "${color}$(printf "" "${1}") ${_message}${reset}" >&2;
|
||||
fi
|
||||
}
|
||||
|
||||
function die () { local _message="${*} Exiting."; echo "$(_alert emergency)"; safeExit;}
|
||||
function error () { local _message="\r${*}"; echo "$(_alert error)" >&2; }
|
||||
function warning () { local _message="\r${*}"; echo "$(_alert warning)" >&2; }
|
||||
function notice () { local _message="\r${*}"; echo "$(_alert notice)" >&2; }
|
||||
function info () { local _message="\r${*}"; echo "$(_alert info)" >&2; }
|
||||
function debug () { local _message="\r${*}"; echo "$(_alert debug)" >&2; }
|
||||
function success () { local _message="\r${*}"; echo "$(_alert success)" >&2; }
|
||||
function input() { local _message="${*}"; echo "$(_alert input)" >&2; }
|
||||
function header() { local _message="\n========== ${*} ==========\n "; echo "$(_alert header)"; }
|
||||
|
||||
# Log messages when verbose is set to "true"
|
||||
verbose() {
|
||||
if [[ "${verbose}" = "true" ]] || [ "${verbose}" == "1" ]; then
|
||||
debug "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Source additional /lib files
|
||||
# ------------------------------------------------------
|
||||
|
||||
# First we locate this script and populate the $SCRIPTPATH variable
|
||||
# Doing so allows us to source additional files from this utils file.
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "${SOURCE}" ]; do # resolve ${SOURCE} until the file is no longer a symlink
|
||||
DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )"
|
||||
SOURCE="$(readlink "${SOURCE}")"
|
||||
[[ ${SOURCE} != /* ]] && SOURCE="${DIR}/${SOURCE}" # if ${SOURCE} was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
SOURCEPATH="$( cd -P "$( dirname "${SOURCE}" )" && pwd )"
|
||||
|
||||
if [ ! -d "${SOURCEPATH}" ]
|
||||
then
|
||||
die "Failed to find library files expected in: ${SOURCEPATH}"
|
||||
fi
|
||||
for utility_file in "${SOURCEPATH}"/*.sh
|
||||
do
|
||||
if [ -e "${utility_file}" ]; then
|
||||
# Don't source self
|
||||
if [[ "${utility_file}" == *"utils.sh"* ]]; then
|
||||
continue
|
||||
fi
|
||||
source "$utility_file"
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Set variables here
|
||||
# ------------------------------------------------------
|
||||
|
||||
# Please comment out the variables of the region you are not using
|
||||
|
||||
# Default region unspecific variables
|
||||
|
||||
default_region=eu-west-2
|
||||
default_key_name=leonradeck-keypair
|
||||
default_key_file='/cygdrive/c/Users/d069485/.ssh/leonradeck-keypair.pem'
|
||||
default_instance_type=t2.medium
|
||||
default_server_startup_notify=leon.radeck@sap.com
|
||||
default_new_admin_password=admin
|
||||
default_user_username=newuser
|
||||
default_user_password=newpassword
|
||||
hosted_zone_id=Z1R8UBAEXAMPLE
|
||||
|
||||
# Variables for region "eu-west-2" (London)
|
||||
|
||||
instance_security_group_ids=sg-871732ee
|
||||
image_id=ami-39f3e25d
|
||||
certificate_arn='arn:aws:acm:eu-west-2:017363970217:certificate/d2ae17b0-75ed-4b56-ac12-a80f9e5c493d'
|
||||
elb_security_group_ids=sg-871732ee
|
||||
mongodb_host=35.176.143.232
|
||||
mongodb_port=27017
|
||||
listener_arn='arn:aws:elasticloadbalancing:eu-west-2:017363970217:listener/app/DummyALB/da70f61a914fc50a/22ab628fba30d0e7'
|
||||
|
||||
# Variables for region "eu-west-1" (Ireland)
|
||||
|
||||
# instance_security_group_ids=sg-eaf31e85
|
||||
# image_id=
|
||||
# certificate_arn=''
|
||||
# elb_security_group_ids=
|
||||
# mongodb_host=54.76.64.42
|
||||
# mongodb_port=27017
|
||||
# listener_arn='arn:aws:elasticloadbalancing:eu-west-1:017363970217:listener/app/DummyALBIreland/a86d2ab6ddfc4f18/bcbbd28a0ca75e54'
|
||||
|
||||
|
||||
# Other Variables
|
||||
|
||||
ssh_retry_interval=2
|
||||
http_retry_interval=5
|
||||
user_data_file=".userdata.txt"
|
||||
change_resource_record_set_file=".change-resource-record-set.json"
|
||||
tag_specifications="\'ResourceType=instance,Tags=[{Key=Name,Value=%s}]\'"
|
||||
admin_username=admin
|
||||
admin_password=admin
|
||||
instance_count=1
|
||||
ssh_user=root
|
||||
default_instance_name="$now"
|
||||
default_instance_short_name="$timestamp"
|
||||
sailing_0='/home/sailing/servers/server/logs/sailing0.log.0'
|
||||
sailing_err='/var/log/sailing.err'
|
||||
sailing_out='/var/log/sailing.out'
|
||||
events_conf='/etc/httpd/conf.d/001-events.conf'
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Production:
|
||||
CACHE=/var/log/old/cache/unique-ips-per-referrer
|
||||
# Test:
|
||||
#CACHE=/var/log/old/cache/unique-ips-per-referrer/test
|
||||
VISITED_FILES=$CACHE/visited
|
||||
STATS=$CACHE/stats
|
||||
OUTPUT=$STATS/results
|
||||
MONTHS=$OUTPUT/permonth
|
||||
TOTALS=$OUTPUT/totals
|
||||
EVENTTOTALS=$OUTPUT/eventtotals
|
||||
|
||||
function monthMapper() {
|
||||
monthname=$1
|
||||
case $monthname in
|
||||
Jan)
|
||||
echo "01"
|
||||
;;
|
||||
Feb)
|
||||
echo "02"
|
||||
;;
|
||||
Mar)
|
||||
echo "03"
|
||||
;;
|
||||
Apr)
|
||||
echo "04"
|
||||
;;
|
||||
May)
|
||||
echo "05"
|
||||
;;
|
||||
Jun)
|
||||
echo "06"
|
||||
;;
|
||||
Jul)
|
||||
echo "07"
|
||||
;;
|
||||
Aug)
|
||||
echo "08"
|
||||
;;
|
||||
Sep)
|
||||
echo "09"
|
||||
;;
|
||||
Oct)
|
||||
echo "10"
|
||||
;;
|
||||
Nov)
|
||||
echo "11"
|
||||
;;
|
||||
Dec)
|
||||
echo "12"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
mkdir -p $OUTPUT
|
||||
rm $OUTPUT/*
|
||||
mkdir -p $MONTHS
|
||||
rm $MONTHS/*
|
||||
|
||||
# Now filter and group by month
|
||||
for i in ${STATS}/*.ips; do
|
||||
hostname=`basename $i .ips`
|
||||
cat $i | while read line; do
|
||||
#echo line is $line
|
||||
read ipaddress dayandmonth useragent <<< "$line"
|
||||
read day month <<< "${dayandmonth/\// }"
|
||||
read monthname year <<< "${month//\// }"
|
||||
monthfile=$MONTHS/${year}-$(monthMapper $monthname)
|
||||
#echo ipaddress is $ipaddress and dayandmonth is $dayandmonth and month is $month and monthname is $monthname and year is $year and monthfile is $monthfile
|
||||
echo "$hostname $line" >>$monthfile
|
||||
echo "$hostname $line" >>$EVENTTOTALS
|
||||
echo "$line" >>$TOTALS
|
||||
done
|
||||
done
|
||||
cat $EVENTTOTALS | sort -u | awk '{ print $1; }' | uniq -c | awk '{ print $2, $1; }' >${EVENTTOTALS}.unique
|
||||
rm $EVENTTOTALS
|
||||
cat $TOTALS | sort -u | wc | awk '{ print $1; }' >${TOTALS}.unique
|
||||
rm $TOTALS
|
||||
for monthfile in $MONTHS/*; do
|
||||
cat $monthfile | sort -u | awk '{ print $1; }' | uniq -c | awk '{ print $2, $1; }' >$monthfile.unique
|
||||
rm $monthfile
|
||||
done
|
||||
echo Done with $0 at `date`
|
||||
@@ -1,4 +1,4 @@
|
||||
# Welcome to the SAP Sailing Wiki
|
||||
# Welcome to the SAP Sailing Wiki
|
||||
|
||||
This is the <img src="https://www.sapsailing.com/images/sap-logo_grey.png"/> Wiki where useful information regarding this project can be found.
|
||||
|
||||
@@ -110,10 +110,13 @@ SAP is at the center of today’s technology revolution, developing innovations
|
||||
* [[Operating Igtimi WindBots|wiki/howto/eventmanagers/windbot-operations]]
|
||||
* [[Linking Race Videos|wiki/howto/eventmanagers/linking-race-videos]]
|
||||
* [[Import official results|wiki/howto/eventmanagers/results-import]]
|
||||
* [[Pairing lists|wiki/howto/eventmanagers/pairing-lists]]
|
||||
* [[Manage media content|wiki/howto/eventmanagers/Manage-media-content]]
|
||||
|
||||
### Setup
|
||||
|
||||
* [[Configure Races on Server|wiki/howto/setup/configure-races-on-server]]
|
||||
* [[Setup local webserver to serve 360° videos|wiki/howto/setup/webserver/chrome-webserver]]
|
||||
* [[Setting up internal Jenkins on SAP Monsoon|wiki/howto/setup/setting-up-jenkins-on-sap-monsoon]]
|
||||
|
||||
### Miscellaneous
|
||||
@@ -138,6 +141,7 @@ SAP is at the center of today’s technology revolution, developing innovations
|
||||
* [[Kieler Woche event page|wiki/events/kieler-woche-2015]]
|
||||
* [[Charleston Race Week 2016|wiki/events/Charleston-Race-Week-2016]]
|
||||
* [[Sailing Leagues 2016|wiki/events/sailing-Leagues-2016]]
|
||||
* [[Media Content|wiki/events/Sailing-events-media-content]]
|
||||
|
||||
## Planning
|
||||
* [[Overview|https://wiki.sapsailing.com/pages/wiki/planning/]]
|
||||
|
||||
@@ -25,7 +25,8 @@ Require-Bundle: com.sap.sailing.domain,
|
||||
com.sap.sailing.udpconnector,
|
||||
com.googlecode.mgwt;bundle-version="2.0.0";resolution:=optional,
|
||||
com.sap.sse.gwt.adminconsole,
|
||||
org.apache.commons.math;bundle-version="2.1.0"
|
||||
org.apache.commons.math;bundle-version="2.1.0",
|
||||
com.sap.sse.security.ui
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Import-Package: com.sap.sse.util,
|
||||
javax.servlet;version="3.1.0",
|
||||
|
||||
+17
-3
@@ -217,9 +217,23 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
|
||||
private int getNumberOf(ManeuverType maneuverType) {
|
||||
TrackedRace trackedRace = getTrackedRace();
|
||||
int number = 0;
|
||||
for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), trackedRace.getStartOfRace(), trackedRace.getEndOfTracking(), false)) {
|
||||
if (maneuver.getType() == maneuverType) {
|
||||
number++;
|
||||
if (trackedRace != null && trackedRace.getStartOfRace() != null) {
|
||||
final TimePoint end;
|
||||
final TimePoint endOfTracking = trackedRace.getEndOfTracking();
|
||||
if (trackedRace.getEndOfRace() != null) {
|
||||
end = trackedRace.getEndOfRace();
|
||||
} else {
|
||||
final TimePoint now = MillisecondsTimePoint.now();
|
||||
if (endOfTracking != null && endOfTracking.before(now)) {
|
||||
end = endOfTracking;
|
||||
} else {
|
||||
end = now;
|
||||
}
|
||||
}
|
||||
for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), trackedRace.getStartOfRace(), end, false)) {
|
||||
if (maneuver.getType() == maneuverType) {
|
||||
number++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return number;
|
||||
|
||||
+5
-1
@@ -4,6 +4,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
@@ -21,8 +22,9 @@ import com.sap.sailing.server.trackfiles.impl.BravoExtendedDataImporterImpl;
|
||||
|
||||
public class BravoGPSFixImporter implements GPSFixImporter {
|
||||
@Override
|
||||
public void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, final String filename)
|
||||
public boolean importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, final String filename)
|
||||
throws FormatNotSupportedException, IOException {
|
||||
final AtomicBoolean importedFixes = new AtomicBoolean(false);
|
||||
TrackFileImportDeviceIdentifier device = new TrackFileImportDeviceIdentifierImpl(filename, getType() + "@" + new Date());
|
||||
new BravoExtendedDataImporterImpl().importFixes(inputStream,
|
||||
(Iterable<DoubleVectorFix> fixes, TrackFileImportDeviceIdentifier deviceIdentifier)->{
|
||||
@@ -34,8 +36,10 @@ public class BravoGPSFixImporter implements GPSFixImporter {
|
||||
new KnotSpeedWithBearingImpl(fix.get(BravoExtendedSensorDataMetadata.SOG.getColumnIndex()),
|
||||
new DegreeBearingImpl(fix.get(BravoExtendedSensorDataMetadata.COG.getColumnIndex()))));
|
||||
callback.addFix(gpsFix, device);
|
||||
importedFixes.set(true);
|
||||
}
|
||||
}, filename, /* sourceName */ getType(), /* downsample */ false);
|
||||
return importedFixes.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ public enum DetailType implements Serializable {
|
||||
RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START(2, DESCENDING),
|
||||
RACE_CALCULATED_TIME_TRAVELED(0, ASCENDING), RACE_CALCULATED_TIME_AT_ESTIMATED_ARRIVAL_AT_COMPETITOR_FARTHEST_AHEAD(0, ASCENDING),
|
||||
RACE_TIME_TRAVELED(0, ASCENDING), RACE_TIME_TRAVELED_UPWIND(0, ASCENDING), RACE_TIME_TRAVELED_DOWNWIND(0, ASCENDING), RACE_TIME_TRAVELED_REACHING(0, ASCENDING),
|
||||
DISTANCE_TO_START_LINE(0, ASCENDING), BEAT_ANGLE(0, ASCENDING), COURSE_OVER_GROUND_TRUE_DEGREES(0, ASCENDING);
|
||||
DISTANCE_TO_START_LINE(0, ASCENDING), BEAT_ANGLE(0, ASCENDING), COURSE_OVER_GROUND_TRUE_DEGREES(0, ASCENDING), TOTAL_SCORED_RACE_COUNT(0, ASCENDING);
|
||||
|
||||
private int precision;
|
||||
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ public class BoatDTO extends NamedDTO implements Serializable {
|
||||
public String getSailId() {
|
||||
return sailId;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
|
||||
+1
@@ -28,6 +28,7 @@ public class LeaderboardRowDTO implements Serializable {
|
||||
public Double totalDistanceTraveledInMeters;
|
||||
public Double totalDurationFoiledInSeconds;
|
||||
public Double totalDistanceFoiledInMeters;
|
||||
public Integer totalScoredRaces;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
+4
@@ -20,6 +20,10 @@ public class MetaLeaderboardRaceColumnDTO extends RaceColumnDTO {
|
||||
public void addRace(BasicRaceDTO race) {
|
||||
raceList.add(race);
|
||||
}
|
||||
|
||||
public ArrayList<BasicRaceDTO> getRaceList() {
|
||||
return raceList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
|
||||
public class PairingListDTO implements Serializable{
|
||||
|
||||
private static final long serialVersionUID = 102220422437194196L;
|
||||
private List<List<List<Pair<CompetitorDTO, BoatDTO>>>> pairingList;
|
||||
private List<String> raceColumnNames;
|
||||
|
||||
public PairingListDTO() { }
|
||||
|
||||
public PairingListDTO(List<List<List<Pair<CompetitorDTO, BoatDTO>>>> result) {
|
||||
this(result, null);
|
||||
}
|
||||
|
||||
public PairingListDTO(List<List<List<Pair<CompetitorDTO, BoatDTO>>>> result, List<String> raceColumnNames) {
|
||||
this.pairingList = result;
|
||||
this.raceColumnNames = raceColumnNames;
|
||||
}
|
||||
|
||||
public List<List<List<Pair<CompetitorDTO, BoatDTO>>>> getPairingList() {
|
||||
return pairingList;
|
||||
}
|
||||
|
||||
public List<BoatDTO> getBoats() {
|
||||
List<BoatDTO> boats = new ArrayList<>();
|
||||
for (List<Pair<CompetitorDTO, BoatDTO>> fleet : this.pairingList.get(0)) {
|
||||
for (Pair<CompetitorDTO, BoatDTO> competitorAndBoatPair : fleet) {
|
||||
if (!boats.contains(competitorAndBoatPair.getB())) {
|
||||
boats.add(competitorAndBoatPair.getB());
|
||||
}
|
||||
}
|
||||
}
|
||||
return boats;
|
||||
}
|
||||
|
||||
public List<String> getRaceColumnNames() {
|
||||
return raceColumnNames;
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class PairingListTemplateDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 7155851765154315798L;
|
||||
|
||||
private int flightCount = 0;
|
||||
private int groupCount = 0;
|
||||
private int competitorCount = 0;
|
||||
private int flightMultiplier = 0;
|
||||
private int[][] pairingListTemplate;
|
||||
private double quality;
|
||||
|
||||
private Iterable<String> selectedFlightNames;
|
||||
|
||||
public PairingListTemplateDTO() { }
|
||||
|
||||
public PairingListTemplateDTO(int competitorCount, int flightMultiplier) {
|
||||
this(0, 0, competitorCount, flightMultiplier, null, 0.0, null);
|
||||
}
|
||||
|
||||
public PairingListTemplateDTO(int competitorCount, int flightMultiplier, int[][] pairingListTemplate, double quality) {
|
||||
this(0, 0, competitorCount, flightMultiplier, pairingListTemplate, quality, null);
|
||||
}
|
||||
|
||||
public PairingListTemplateDTO(int competitorCount, int[][] pairingListTemplate, double quality) {
|
||||
this(0, 0, competitorCount, 0, pairingListTemplate, quality, null);
|
||||
}
|
||||
|
||||
public PairingListTemplateDTO(int flightCount, int groupCount, int competitorCount, int flightMultiplier, int[][] pairingListTemplate, double quality) {
|
||||
this(flightCount, groupCount, competitorCount, flightMultiplier, pairingListTemplate, quality, null);
|
||||
}
|
||||
|
||||
public PairingListTemplateDTO(int flightCount, int groupCount, int competitorCount, int flightMultiplier, int[][] pairingListTemplate, double quality, Iterable<String> selectedFlightNames) {
|
||||
this.flightCount = flightCount;
|
||||
this.groupCount = groupCount;
|
||||
this.competitorCount = competitorCount;
|
||||
this.flightMultiplier = flightMultiplier;
|
||||
this.quality = quality;
|
||||
this.pairingListTemplate = pairingListTemplate;
|
||||
this.selectedFlightNames = selectedFlightNames;
|
||||
}
|
||||
|
||||
public void setFlightCount(int flightCount){
|
||||
this.flightCount=flightCount;
|
||||
}
|
||||
public void setGroupCount(int groupCount) {
|
||||
this.groupCount = groupCount;
|
||||
}
|
||||
|
||||
public void setFlightMultiplier(int flightMultiplier) {
|
||||
this.flightMultiplier = flightMultiplier;
|
||||
}
|
||||
|
||||
public void setSelectedFlightNames(Iterable<String> selectedFlightNames) {
|
||||
this.selectedFlightNames = selectedFlightNames;
|
||||
}
|
||||
|
||||
public int[][] getPairingListTemplate() {
|
||||
return this.pairingListTemplate;
|
||||
}
|
||||
|
||||
public double getQuality() {
|
||||
return this.quality;
|
||||
}
|
||||
|
||||
public int getFlightCount() {
|
||||
return this.flightCount;
|
||||
}
|
||||
|
||||
public int getGroupCount() {
|
||||
return this.groupCount;
|
||||
}
|
||||
|
||||
public int getCompetitorCount() {
|
||||
return this.competitorCount;
|
||||
}
|
||||
|
||||
public int getFlightMultiplier() {
|
||||
return flightMultiplier;
|
||||
}
|
||||
|
||||
public Iterable<String> getSelectedFlightNames() {
|
||||
return selectedFlightNames;
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
public class VideoMetadataDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private boolean canDownload;
|
||||
private Date recordStartedTime;
|
||||
private boolean spherical;
|
||||
private String message;
|
||||
|
||||
public VideoMetadataDTO() {
|
||||
|
||||
}
|
||||
|
||||
public VideoMetadataDTO(boolean canDownload, boolean spherical, Date recordStartedTime, String message) {
|
||||
this.canDownload = canDownload;
|
||||
this.spherical = spherical;
|
||||
this.recordStartedTime = recordStartedTime;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public boolean isDownloadable() {
|
||||
return canDownload;
|
||||
}
|
||||
|
||||
public boolean isSpherical() {
|
||||
return spherical;
|
||||
}
|
||||
|
||||
public Date getRecordStartedTime() {
|
||||
return recordStartedTime;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
+1
@@ -6,6 +6,7 @@ package com.sap.sailing.domain.common.security;
|
||||
public enum Permission implements com.sap.sse.security.shared.Permission {
|
||||
// AdminConsole permissions
|
||||
MANAGE_EVENTS,
|
||||
MANAGE_PAIRING_LISTS,
|
||||
MANAGE_REGATTAS,
|
||||
MANAGE_TRACKED_RACES,
|
||||
SHOW_TRACKED_RACES,
|
||||
|
||||
+6
-1
@@ -6,6 +6,7 @@ import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
|
||||
import com.sap.sailing.domain.deckmanadapter.DeckmanAdapter;
|
||||
@@ -18,16 +19,20 @@ import com.sap.sailing.domain.trackimport.GPSFixImporter;
|
||||
public class DeckmanGPSFixImporter implements GPSFixImporter {
|
||||
|
||||
@Override
|
||||
public void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, String sourceName)
|
||||
public boolean importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing,
|
||||
String sourceName)
|
||||
throws FormatNotSupportedException, IOException {
|
||||
DeckmanAdapter deckmanAdapter = DeckmanAdapterFactoryImpl.INSTANCE.createDeckmanAdapter();
|
||||
TrackFileImportDeviceIdentifier device = new TrackFileImportDeviceIdentifierImpl(sourceName, getType() + "@" + new Date());
|
||||
final AtomicBoolean importedFixes = new AtomicBoolean(false);
|
||||
try {
|
||||
for (Iterator<Record> i = deckmanAdapter.parseLogFile(new InputStreamReader(inputStream)); i.hasNext();) {
|
||||
Record record = i.next();
|
||||
callback.addFix(new GPSFixMovingImpl(record.getPosition(), record.getTimePoint(), record.getGpsFix().getSpeed()),
|
||||
device);
|
||||
importedFixes.set(true);
|
||||
}
|
||||
return importedFixes.get();
|
||||
} catch (RuntimeException e) {
|
||||
if (e.getCause() != null) {
|
||||
if (e.getCause() instanceof IOException) {
|
||||
|
||||
+42
-21
@@ -4,9 +4,9 @@ import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
@@ -18,44 +18,65 @@ import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
|
||||
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl;
|
||||
import com.sap.sailing.domain.trackimport.FormatNotSupportedException;
|
||||
import com.sap.sailing.domain.trackimport.GPSFixImporter;
|
||||
import com.sap.sailing.server.trackfiles.impl.CompressedStreamsUtil;
|
||||
import com.sap.sailing.server.trackfiles.impl.ExpeditionExtendedDataImporterImpl;
|
||||
import com.sap.sailing.server.trackfiles.impl.ExpeditionImportFileHandler;
|
||||
|
||||
public class ExpeditionGPSFixImporter implements GPSFixImporter {
|
||||
private static final String LAT_COLUMN_HEADING = ExpeditionExtendedDataImporterImpl.COL_NAME_LAT;
|
||||
private static final String LON_COLUMN_HEADING = ExpeditionExtendedDataImporterImpl.COL_NAME_LON;
|
||||
private static final String COG_COLUMN_HEADING = "Cog";
|
||||
private static final String SOG_COLUMN_HEADING = "Sog";
|
||||
|
||||
@Override
|
||||
public void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, final String sourceName)
|
||||
public boolean importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing,
|
||||
final String sourceName)
|
||||
throws FormatNotSupportedException, IOException {
|
||||
TrackFileImportDeviceIdentifier device = new TrackFileImportDeviceIdentifierImpl(sourceName, getType() + "@" + new Date());
|
||||
final BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
|
||||
final String headerLine = br.readLine();
|
||||
final Map<String, Integer> columnDefinitions = ExpeditionExtendedDataImporterImpl.parseHeader(headerLine);
|
||||
final AtomicInteger lineNr = new AtomicInteger(0);
|
||||
br.lines().forEach(line->{
|
||||
if (!line.trim().isEmpty()) {
|
||||
ExpeditionExtendedDataImporterImpl.parseLine(lineNr.incrementAndGet(), sourceName, line, columnDefinitions,
|
||||
(timePoint, columnValues, columns)->{
|
||||
final double latDeg = Double.parseDouble(columnValues[columns.get(LAT_COLUMN_HEADING)]);
|
||||
final double lonDeg = Double.parseDouble(columnValues[columns.get(LON_COLUMN_HEADING)]);
|
||||
final double cogDeg = Double.parseDouble(columnValues[columns.get(COG_COLUMN_HEADING)]);
|
||||
final double sogKnots = Double.parseDouble(columnValues[columns.get(SOG_COLUMN_HEADING)]);
|
||||
final GPSFixMoving fix = new GPSFixMovingImpl(new DegreePosition(latDeg, lonDeg), timePoint,
|
||||
new KnotSpeedWithBearingImpl(sogKnots, new DegreeBearingImpl(cogDeg)));
|
||||
callback.addFix(fix, device);
|
||||
final AtomicBoolean importedFixes = new AtomicBoolean(false);
|
||||
CompressedStreamsUtil.handlePotentiallyCompressedFiles(sourceName, inputStream,
|
||||
new ExpeditionImportFileHandler() {
|
||||
|
||||
@Override
|
||||
protected void handleExpeditionFile(String fileName, InputStream stream) throws IOException {
|
||||
final BufferedReader br = new BufferedReader(new InputStreamReader(stream));
|
||||
final String headerLine = br.readLine();
|
||||
final Map<String, Integer> columnDefinitions = ExpeditionExtendedDataImporterImpl
|
||||
.parseHeader(headerLine);
|
||||
final AtomicInteger lineNr = new AtomicInteger(0);
|
||||
br.lines().forEach(line -> {
|
||||
if (!line.trim().isEmpty()) {
|
||||
ExpeditionExtendedDataImporterImpl.parseLine(lineNr.incrementAndGet(), fileName, line,
|
||||
columnDefinitions, (timePoint, columnValues, columns) -> {
|
||||
final double latDeg = Double
|
||||
.parseDouble(columnValues[columns.get(LAT_COLUMN_HEADING)]);
|
||||
final double lonDeg = Double
|
||||
.parseDouble(columnValues[columns.get(LON_COLUMN_HEADING)]);
|
||||
final double cogDeg = Double
|
||||
.parseDouble(columnValues[columns.get(COG_COLUMN_HEADING)]);
|
||||
final double sogKnots = Double
|
||||
.parseDouble(columnValues[columns.get(SOG_COLUMN_HEADING)]);
|
||||
final GPSFixMoving fix = new GPSFixMovingImpl(
|
||||
new DegreePosition(latDeg, lonDeg), timePoint,
|
||||
new KnotSpeedWithBearingImpl(sogKnots,
|
||||
new DegreeBearingImpl(cogDeg)));
|
||||
callback.addFix(fix, device);
|
||||
importedFixes.set(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return importedFixes.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<String> getSupportedFileExtensions() {
|
||||
return Arrays.asList(new String[] { "csv", "log", "txt" });
|
||||
return ExpeditionImportFileHandler.supportedExpeditionLogFileExtensions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return "Expedition";
|
||||
return GPSFixImporter.EXPEDITION_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.domain.persistence;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -167,7 +168,7 @@ public interface MongoObjectFactory {
|
||||
*
|
||||
* @see #addConnectivityParametersForRaceToRestore(RaceTrackingConnectivityParameters)
|
||||
*/
|
||||
void removeConnectivityParametersForRaceToRestore(RaceTrackingConnectivityParameters params);
|
||||
void removeConnectivityParametersForRaceToRestore(RaceTrackingConnectivityParameters params) throws MalformedURLException;
|
||||
|
||||
/**
|
||||
* Updates the database such that the next call to
|
||||
|
||||
+4
-1
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.domain.persistence.impl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -1590,7 +1591,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConnectivityParametersForRaceToRestore(RaceTrackingConnectivityParameters params) {
|
||||
public void removeConnectivityParametersForRaceToRestore(RaceTrackingConnectivityParameters params) throws MalformedURLException {
|
||||
if (raceTrackingConnectivityParamsServiceFinder != null) {
|
||||
final String typeIdentifier = params.getTypeIdentifier();
|
||||
final RaceTrackingConnectivityParametersHandler paramsPersistenceService = raceTrackingConnectivityParamsServiceFinder.findService(typeIdentifier);
|
||||
@@ -1625,6 +1626,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
} catch (NoCorrespondingServiceRegisteredException e) {
|
||||
logger.log(Level.WARNING, "Couldn't find a persistence service for connectivity parameters of type "+typeIdentifier+
|
||||
". Couldn't store race "+params.getTrackerID()+" for restoring.", e);
|
||||
} catch (MalformedURLException e) {
|
||||
logger.log(Level.WARNING, "Issue with reading a URL from the tracking params for tracker "+params.getTrackerID()+" for restoring.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -74,8 +74,12 @@ public interface RaceLogTrackingAdapter {
|
||||
* Denotes the entire {@link Leaderboard} for racelog-tracking, by calling the
|
||||
* {@link #denoteRaceForRaceLogTracking(RacingEventService, Leaderboard, RaceColumn, Fleet, String)} method for each
|
||||
* {@link RaceLog}.
|
||||
*
|
||||
* @param prefix Use this parameter to set the racename in the denoteEvent. The prefix will be used for all races.
|
||||
* Additional to the prefix there will be a serial number that gives every race a individual name. You can pass null
|
||||
* to get the default denote name. The default looks like: regatta name + racecolumn name + race name.
|
||||
*/
|
||||
void denoteAllRacesForRaceLogTracking(RacingEventService service, Leaderboard leaderboard)
|
||||
void denoteAllRacesForRaceLogTracking(RacingEventService service, Leaderboard leaderboard, String prefix)
|
||||
throws NotDenotableForRaceLogTrackingException;
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ public class RaceLogConnectivityParams extends AbstractRaceTrackingConnectivityP
|
||||
throw new RaceNotCreatedException("No regatta for race-log tracked race");
|
||||
}
|
||||
DynamicTrackedRegatta trackedRegatta = trackedRegattaRegistry.getOrCreateTrackedRegatta(regatta);
|
||||
return new RaceLogRaceTracker(trackedRegatta, this, windStore, raceLogResolver, this);
|
||||
return new RaceLogRaceTracker(trackedRegatta, this, windStore, raceLogResolver, this, trackedRegattaRegistry);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+42
-6
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.domain.racelogtracking.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -47,6 +48,7 @@ import com.sap.sailing.domain.base.Sideline;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.CourseDataImpl;
|
||||
import com.sap.sailing.domain.base.impl.CourseImpl;
|
||||
import com.sap.sailing.domain.base.impl.RaceColumnListenerWithDefaultAction;
|
||||
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
|
||||
import com.sap.sailing.domain.common.PassingInstruction;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
@@ -61,6 +63,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
|
||||
import com.sap.sailing.domain.tracking.RaceHandle;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
import com.sap.sailing.domain.tracking.WindStore;
|
||||
import com.sap.sailing.domain.tracking.WindTrack;
|
||||
import com.sap.sse.common.Util;
|
||||
@@ -91,12 +94,14 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
|
||||
private final WindStore windStore;
|
||||
private final DynamicTrackedRegatta trackedRegatta;
|
||||
private final RaceLogResolver raceLogResolver;
|
||||
private final TrackedRegattaRegistry trackedRegattaRegistry;
|
||||
|
||||
private volatile DynamicTrackedRace trackedRace;
|
||||
|
||||
public RaceLogRaceTracker(DynamicTrackedRegatta regatta, RaceLogConnectivityParams params, WindStore windStore,
|
||||
RaceLogResolver raceLogResolver, RaceLogConnectivityParams connectivityParams) {
|
||||
RaceLogResolver raceLogResolver, RaceLogConnectivityParams connectivityParams, TrackedRegattaRegistry trackedRegattaRegistry) {
|
||||
super(connectivityParams);
|
||||
this.trackedRegattaRegistry = trackedRegattaRegistry;
|
||||
this.params = params;
|
||||
this.windStore = windStore;
|
||||
this.trackedRegatta = regatta;
|
||||
@@ -144,16 +149,13 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
|
||||
public void visit(RegattaLogDefineMarkEvent event) {
|
||||
RaceLogRaceTracker.this.onDefineMarkEvent(event);
|
||||
}
|
||||
|
||||
};
|
||||
visitors.put(log, visitor);
|
||||
((RegattaLog) log).addListener(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(String.format("Created race-log tracker for: %s %s %s", params.getLeaderboard(),
|
||||
params.getRaceColumn(), params.getFleet()));
|
||||
|
||||
// load race for which tracking already started
|
||||
if (new RaceLogTrackingStateAnalyzer(params.getRaceLog()).analyze() == RaceLogTrackingState.TRACKING) {
|
||||
startTracking(null);
|
||||
@@ -252,7 +254,11 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
|
||||
update.add(new Util.Pair<>(waypoint.getControlPoint(), waypoint.getPassingInstructions()));
|
||||
}
|
||||
try {
|
||||
trackedRace.getRace().getCourse().update(update, params.getDomainFactory());
|
||||
final Course course = trackedRace.getRace().getCourse();
|
||||
course.update(update, params.getDomainFactory());
|
||||
if (base.getName() != null) {
|
||||
course.setName(base.getName());
|
||||
}
|
||||
} catch (PatchFailedException e) {
|
||||
logger.log(Level.WARNING, "Could not update course for race " + trackedRace.getRace().getName());
|
||||
}
|
||||
@@ -277,7 +283,7 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
|
||||
courseBase = new CourseDataImpl("Default course for " + raceName);
|
||||
logger.log(Level.FINE, "Using empty course in creation of race " + raceName);
|
||||
}
|
||||
final Course course = new CourseImpl(raceName + " course", courseBase.getWaypoints());
|
||||
final Course course = new CourseImpl(courseBase.getName() == null ? raceName + " course" : courseBase.getName(), courseBase.getWaypoints());
|
||||
if (raceColumn.getTrackedRace(fleet) != null) {
|
||||
if (event != null) {
|
||||
try {
|
||||
@@ -305,5 +311,35 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
|
||||
synchronized (this) {
|
||||
this.notifyAll();
|
||||
}
|
||||
registerListenerThatWillRemoveThisRaceWhenTheRaceColumnIsRemoved();
|
||||
}
|
||||
|
||||
private void registerListenerThatWillRemoveThisRaceWhenTheRaceColumnIsRemoved() {
|
||||
getRegatta().addRaceColumnListener(new RaceColumnListenerWithDefaultAction() {
|
||||
private static final long serialVersionUID = -2924864263579432528L;
|
||||
|
||||
@Override
|
||||
public void defaultAction() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceColumnRemovedFromContainer(RaceColumn raceColumn) {
|
||||
try {
|
||||
trackedRegattaRegistry.removeRace(getRegatta(), getRace());
|
||||
} catch (IOException | InterruptedException e) {
|
||||
logger.log(Level.WARNING, "Error trying to remove smart phone / race log tracked race whose race column was deleted: "+
|
||||
e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This listener is transient and will therefore not be serialized to any replicas or with
|
||||
* the master data import.
|
||||
*/
|
||||
@Override
|
||||
public boolean isTransient() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+9
-3
@@ -172,11 +172,17 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void denoteAllRacesForRaceLogTracking(final RacingEventService service, final Leaderboard leaderboard)
|
||||
throws NotDenotableForRaceLogTrackingException {
|
||||
public void denoteAllRacesForRaceLogTracking(final RacingEventService service, final Leaderboard leaderboard,
|
||||
final String prefix) throws NotDenotableForRaceLogTrackingException {
|
||||
int fleetcount = 1;
|
||||
for (RaceColumn column : leaderboard.getRaceColumns()) {
|
||||
for (Fleet fleet : column.getFleets()) {
|
||||
denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, null);
|
||||
if (prefix != null) {
|
||||
denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, prefix + fleetcount);
|
||||
} else {
|
||||
denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, null);
|
||||
}
|
||||
fleetcount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -49,4 +49,9 @@ public class RaceLogDenoteForTrackingEventImpl extends RaceLogEventImpl implemen
|
||||
public Serializable getRaceId() {
|
||||
return raceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortInfo() {
|
||||
return getRaceName();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,8 +4,8 @@ import java.net.URL;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
import com.sap.sse.common.NamedWithID;
|
||||
import com.sap.sse.common.Renamable;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.media.ImageSize;
|
||||
import com.sap.sse.shared.media.WithMedia;
|
||||
|
||||
+1
-1
@@ -2,8 +2,8 @@ package com.sap.sailing.domain.base;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
import com.sap.sse.common.NamedWithID;
|
||||
import com.sap.sse.common.Renamable;
|
||||
|
||||
public interface LeaderboardGroupBase extends Renamable, NamedWithID {
|
||||
UUID getId();
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package com.sap.sailing.domain.base;
|
||||
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
import com.sap.sse.common.Renamable;
|
||||
|
||||
/**
|
||||
* Base interface for series consisting of static information describing the Series.
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.sap.sailing.domain.base;
|
||||
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
import com.sap.sse.common.Named;
|
||||
import com.sap.sse.common.Renamable;
|
||||
|
||||
/**
|
||||
* Describes the place where a sailing event with one or more regattas takes place.
|
||||
|
||||
+1
-1
@@ -3,9 +3,9 @@ package com.sap.sailing.domain.base.impl;
|
||||
import java.net.URI;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
import com.sap.sse.common.Color;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.Renamable;
|
||||
|
||||
public interface DynamicCompetitor extends Competitor, Renamable {
|
||||
DynamicBoat getBoat();
|
||||
|
||||
BIN
Binary file not shown.
+983
@@ -0,0 +1,983 @@
|
||||
//stored-uri:datafiles/66ccec10-b77d-0132-26ea-60a44ce903c3.mtb
|
||||
StartReplay:1
|
||||
|
||||
// Input from params_static:
|
||||
|
||||
// Input from event_parameters:
|
||||
|
||||
DefaultPanel:
|
||||
ReCalcDirAndSpeed:1
|
||||
CompMarkerUUID:2dee0384-2c45-11e4-a9f6-14dae9614d7b
|
||||
CodanIsSponsor:0
|
||||
DefaultTailLength:60
|
||||
EnableResults:1
|
||||
ResultsLayout:[{"key":"Order","header":"#","description":"Position","printable":true,"align":"right","collapsible":false},{"key":"Name","header":"Name","description":"Competitor's name","printable":true,"align":"left","collapsible":false},{"key":"ShortName","header":"Short Name","description":"Competitor's short name","printable":true,"align":"left","collapsible":true},{"key":"LegTime","header":"Leg","description":"Leg time","printable":false,"align":"right","collapsible":true},{"key":"LegPosition","header":"#","description":"Leg position","printable":false,"align":"right","collapsible":true},{"key":"Time","header":"Total","description":"Total time","printable":true,"align":"right","collapsible":false},{"key":"After","header":"Total Δ","description":"Total Δ","printable":true,"align":"right","collapsible":false}]
|
||||
PerformanceTableLayout:[{"key":"Pos","header":"Pos"},{"key":"Name","header":"Name"},{"key":"Short","header":"Short"},{"key":"Country","header":"Country"},{"key":"After","header":"After"},{"key":"Leg pos","header":"Leg pos"},{"key":"Leg time","header":"Leg time"},{"key":"SOG","header":"SOG"},{"key":"VMG","header":"VMG"},{"key":"Top speed","header":"Top Speed"},{"key":"Distance","header":"Distance"}]
|
||||
APzoomChangeTime:2500
|
||||
disableAutoProduce:0
|
||||
autoProduce:0
|
||||
APstartLineShowOfLeg:0.25
|
||||
APsideOfStartLine:0.30
|
||||
APstartSwitch:0.8
|
||||
APwholeLegBehindMarks:0.15
|
||||
APmarkSwitch:0.13
|
||||
APmarkZoomBehindMarks:0.10
|
||||
APmarkZoomInFrontOfMarks:0.20
|
||||
PopupMarkerUUID:84199024-2c45-11e4-a9f6-14dae9614d7b
|
||||
ManualUrl:http://www.tractrac.com/files/userguide_sailing.pdf
|
||||
Marker1UUID:2dee0384-2c45-11e4-a9f6-14dae9614d7b
|
||||
Marker1Type:RGBMarkerFactory
|
||||
Marker1Directional:1
|
||||
Marker1Image:markers/boat_FF0000_big.png
|
||||
Marker1Length:5
|
||||
Marker1MinPixelSize:30
|
||||
Startlocation:25.66219980468759,-33.95794373375933
|
||||
MaxDiffTime:1200
|
||||
MaxMetersPerSecond:20
|
||||
PrecisionMeters:20
|
||||
ShortAvgTime:180
|
||||
LongAvgTime:900
|
||||
TimeZone:2
|
||||
CompMarkerTimeout:60
|
||||
FramesPerSecond:8
|
||||
replayspeedup:5
|
||||
MatchRaceClock:0
|
||||
MatchRaceClockNo:3
|
||||
MatchRaceClockSpacing:300
|
||||
Minimap:1
|
||||
MinimapUseLayers:2
|
||||
Minimapgeorect:25.537683105468773,-33.741288381182635,26.19746406249999,-34.12630292526923
|
||||
MinimapMarkerUUID:84199024-2c45-11e4-a9f6-14dae9614d7b
|
||||
Marker4UUID:84199024-2c45-11e4-a9f6-14dae9614d7b
|
||||
Marker4Type:RGBmarkerFactory
|
||||
Marker4Directional:1
|
||||
Marker4Image:markers/boat_FF0000_small.png
|
||||
MetersPerPixel:18
|
||||
MetersPerPixelMax:30
|
||||
MetersPerPixelMin:0.2
|
||||
isMatchRace:0
|
||||
DefaultDistanceUnits:meters
|
||||
weatherEnabled:0
|
||||
DefaultSpeedUnits:Knots
|
||||
showRaceClock:1
|
||||
BackGroundImage:graphics/background.jpg
|
||||
DisableRotation:0
|
||||
ControlPointMarkerRadius:13
|
||||
disableselection:0
|
||||
disableleaderline:0
|
||||
StartTimeType:Race
|
||||
ResultColumn5SpeedIn:Knots
|
||||
DisableChangeRace:0
|
||||
DisableLatestRoundings:0
|
||||
//live-uri:tcp://secondary.traclive.dk:4400
|
||||
//stored-uri:tcp://secondary.traclive.dk:4401
|
||||
PopUpRow1Name:
|
||||
PopUpRow1Type:Name
|
||||
PopUpRow2Name:Knots
|
||||
PopUpRow2Type:BoatSpeed
|
||||
PopUpRow3Name:Ranking
|
||||
PopUpRow3Type:RowNumber
|
||||
PopUpRow4Name:Sail
|
||||
PopUpRow4Type:Ini
|
||||
PopUpRow5Name:After
|
||||
PopUpRow5Type:BoatAfter
|
||||
WindEstimator:0
|
||||
MapUrl:http://tile.traclive.dk/ttm
|
||||
ResultColumn1Name:#
|
||||
ResultColumn1Size:20
|
||||
ResultColumn1Type:RowNumber
|
||||
ResultColumn2Name:sail
|
||||
ResultColumn2Size:60
|
||||
ResultColumn2Type:Ini
|
||||
ResultColumn3Name:name
|
||||
ResultColumn3Size:110
|
||||
ResultColumn3Type:Name
|
||||
ResultColumn4Name:after
|
||||
ResultColumn4Size:45
|
||||
ResultColumn4Type:BoatAfter
|
||||
ResultColumn5Name:kts
|
||||
ResultColumn5Size:40
|
||||
ResultColumn5Type:BoatSpeed
|
||||
DisableLeaderboard:0
|
||||
DisableAnalysis:0
|
||||
TableFontSize:11
|
||||
compInitialsFontSize:11
|
||||
AutoRotate:0
|
||||
MassStart:0
|
||||
SegmentFile:segments.php
|
||||
RaceHandicapSystem:NONE
|
||||
TeamCompetitionType:NONE
|
||||
LiveDelaySecs: 10
|
||||
|
||||
//-------- AUTO GENERATED PARAMETERS START ----------------
|
||||
|
||||
EventID:b07bb320-b10c-0132-0752-60a44ce903c3
|
||||
EventName:505 Worlds 2015
|
||||
EventDB:event_20150323_Worlds
|
||||
EventStartTime:2015-03-22 22:00:00
|
||||
EventEndTime:2015-04-03 21:00:00
|
||||
EventType:a1b74ca0-fdd8-11dc-8811-005056c00008
|
||||
|
||||
RaceID:66ccec10-b77d-0132-26ea-60a44ce903c3
|
||||
RaceName:Worlds 7
|
||||
RaceStartTime:2015-04-02 12:40:00
|
||||
RaceEndTime:
|
||||
RaceTrackingStartTime:2015-04-02 12:36:00
|
||||
RaceTrackingEndTime:2015-04-02 15:10:00
|
||||
RaceDefaultRouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
RaceHandicapSystem:NONE
|
||||
|
||||
Image1UUID:53955ea0-b10f-0132-075b-60a44ce903c3
|
||||
Image1Type:Logo
|
||||
Image1Description:banner
|
||||
Image1Image:pictures/53955ea0-b10f-0132-075b-60a44ce903c3/original_53955ea0-b10f-0132-075b-60a44ce903c3.png
|
||||
|
||||
Class1UUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Class1Name:5O5
|
||||
|
||||
Team1UUID:5f8d2600-b4fc-0132-1da5-60a44ce903c3
|
||||
Team1Name:0
|
||||
|
||||
Team2UUID:94c361d0-b4db-0132-1cde-60a44ce903c3
|
||||
Team2Name:?
|
||||
|
||||
Team3UUID:ac8787b0-b377-0132-11d3-60a44ce903c3
|
||||
Team3Name:Aaron Ross
|
||||
|
||||
Team4UUID:94cd2760-b4db-0132-1ce2-60a44ce903c3
|
||||
Team4Name:Aeolians
|
||||
Team4Comp1UUID:94d00ef0-b4db-0132-1ce3-60a44ce903c3
|
||||
|
||||
Team5UUID:9589e150-b4db-0132-1d1d-60a44ce903c3
|
||||
Team5Name:Alamitos Bay Yacht Club
|
||||
Team5Comp1UUID:958bd200-b4db-0132-1d1e-60a44ce903c3
|
||||
|
||||
Team6UUID:ac6fff40-b377-0132-11b9-60a44ce903c3
|
||||
Team6Name:Albrecht Holm
|
||||
|
||||
Team7UUID:ac6d2a40-b377-0132-11b1-60a44ce903c3
|
||||
Team7Name:Alexander Ham
|
||||
|
||||
Team8UUID:951939c0-b4db-0132-1cfa-60a44ce903c3
|
||||
Team8Name:Algoa Bay Yacht Club
|
||||
Team8Comp1UUID:951a3330-b4db-0132-1cfb-60a44ce903c3
|
||||
|
||||
Team9UUID:ac6c7560-b377-0132-11af-60a44ce903c3
|
||||
Team9Name:Andrew Arthur
|
||||
|
||||
Team10UUID:ac6b0950-b377-0132-11ab-60a44ce903c3
|
||||
Team10Name:Angela Stenger
|
||||
|
||||
Team11UUID:ac6e8e60-b377-0132-11b5-60a44ce903c3
|
||||
Team11Name:Anthony Parker
|
||||
|
||||
Team12UUID:948565c0-b4db-0132-1cca-60a44ce903c3
|
||||
Team12Name:B&SYC
|
||||
Team12Comp1UUID:94865d50-b4db-0132-1ccb-60a44ce903c3
|
||||
|
||||
Team13UUID:ac61c890-b377-0132-1195-60a44ce903c3
|
||||
Team13Name:Brett Bowden
|
||||
|
||||
Team14UUID:ac861d30-b377-0132-11cf-60a44ce903c3
|
||||
Team14Name:Bronwen KlaasÂ
|
||||
|
||||
Team15UUID:c5bcd7f0-b377-0132-11de-60a44ce903c3
|
||||
Team15Name:Bronwen Klaas
|
||||
|
||||
Team16UUID:9470c9c0-b4db-0132-1cc2-60a44ce903c3
|
||||
Team16Name:BSYC
|
||||
Team16Comp1UUID:9471bf20-b4db-0132-1cc3-60a44ce903c3
|
||||
|
||||
Team17UUID:45679ec0-b6e4-0132-227e-60a44ce903c3
|
||||
Team17Name:BYC
|
||||
Team17Comp1UUID:4568fe80-b6e4-0132-227f-60a44ce903c3
|
||||
|
||||
Team18UUID:94e4cf90-b4db-0132-1ce6-60a44ce903c3
|
||||
Team18Name:Emarentia Sailing Club
|
||||
Team18Comp1UUID:94e60210-b4db-0132-1ce7-60a44ce903c3
|
||||
|
||||
Team19UUID:ac7415b0-b377-0132-11c5-60a44ce903c3
|
||||
Team19Name:Euan Hurter
|
||||
|
||||
Team20UUID:ac691ca0-b377-0132-11a5-60a44ce903c3
|
||||
Team20Name:Georg Mittermayer
|
||||
|
||||
Team21UUID:3c5056f0-b5c5-0132-2049-60a44ce903c3
|
||||
Team21Name:George Lakes Yacht Club
|
||||
Team21Comp1UUID:3c51d930-b5c5-0132-204a-60a44ce903c3
|
||||
|
||||
Team22UUID:ac6ddae0-b377-0132-11b3-60a44ce903c3
|
||||
Team22Name:Gideon Snyman
|
||||
|
||||
Team23UUID:ac83e910-b377-0132-11c9-60a44ce903c3
|
||||
Team23Name:Grant Ballentyne
|
||||
|
||||
Team24UUID:ac84a3d0-b377-0132-11cb-60a44ce903c3
|
||||
Team24Name:Hans Rogotski
|
||||
|
||||
Team25UUID:5f848520-b4fc-0132-1da1-60a44ce903c3
|
||||
Team25Name:Hellerup
|
||||
|
||||
Team26UUID:9491d840-b4db-0132-1cce-60a44ce903c3
|
||||
Team26Name:HISC
|
||||
Team26Comp1UUID:949331b0-b4db-0132-1ccf-60a44ce903c3
|
||||
|
||||
Team27UUID:ac8b0a60-b377-0132-11d7-60a44ce903c3
|
||||
Team27Name:Howard HamlinÂ
|
||||
|
||||
Team28UUID:c5bfb860-b377-0132-11e2-60a44ce903c3
|
||||
Team28Name:Howard Hamlin
|
||||
|
||||
Team29UUID:467d1590-b6e4-0132-22b9-60a44ce903c3
|
||||
Team29Name:HS
|
||||
Team29Comp1UUID:5f853dc0-b4fc-0132-1da2-60a44ce903c3
|
||||
|
||||
Team30UUID:460cd720-b6e4-0132-22a6-60a44ce903c3
|
||||
Team30Name:HYC
|
||||
Team30Comp1UUID:94c44e80-b4db-0132-1cdf-60a44ce903c3
|
||||
|
||||
Team31UUID:ac669050-b377-0132-11a3-60a44ce903c3
|
||||
Team31Name:Ian Pinnel
|
||||
|
||||
Team32UUID:45b19e00-b6e4-0132-2290-60a44ce903c3
|
||||
Team32Name:Imperial Yacht Club
|
||||
Team32Comp1UUID:45b290d0-b6e4-0132-2291-60a44ce903c3
|
||||
|
||||
Team33UUID:ac70ad50-b377-0132-11bb-60a44ce903c3
|
||||
Team33Name:James Largier
|
||||
|
||||
Team34UUID:ac647920-b377-0132-119d-60a44ce903c3
|
||||
Team34Name:Jan Saugmann
|
||||
|
||||
Team35UUID:ac86d340-b377-0132-11d1-60a44ce903c3
|
||||
Team35Name:Jay D'EngleÂ
|
||||
|
||||
Team36UUID:c5bdbd80-b377-0132-11e0-60a44ce903c3
|
||||
Team36Name:Jay D'Engle
|
||||
|
||||
Team37UUID:ac72b370-b377-0132-11c1-60a44ce903c3
|
||||
Team37Name:Jonathan Ham
|
||||
|
||||
Team38UUID:ac6a5be0-b377-0132-11a9-60a44ce903c3
|
||||
Team38Name:Katharina Menge
|
||||
|
||||
Team39UUID:ac720620-b377-0132-11bf-60a44ce903c3
|
||||
Team39Name:Kevin Foreman
|
||||
|
||||
Team40UUID:ac856100-b377-0132-11cd-60a44ce903c3
|
||||
Team40Name:Kobus HoltzhausenÂ
|
||||
|
||||
Team41UUID:c5bc0100-b377-0132-11dc-60a44ce903c3
|
||||
Team41Name:Kobus Holtzhausen
|
||||
|
||||
Team42UUID:ac831b90-b377-0132-11c7-60a44ce903c3
|
||||
Team42Name:Kristina Plattner
|
||||
|
||||
Team43UUID:ac6f46b0-b377-0132-11b7-60a44ce903c3
|
||||
Team43Name:Kyle Klaas
|
||||
|
||||
Team44UUID:ac6277f0-b377-0132-1197-60a44ce903c3
|
||||
Team44Name:Malcolm Higgins
|
||||
|
||||
Team45UUID:94eefa70-b4db-0132-1cea-60a44ce903c3
|
||||
Team45Name:Maties
|
||||
|
||||
Team46UUID:ac5951f0-b377-0132-1192-60a44ce903c3
|
||||
Team46Name:Michale Quirk
|
||||
|
||||
Team47UUID:ac884150-b377-0132-11d5-60a44ce903c3
|
||||
Team47Name:Mike Holt
|
||||
|
||||
Team48UUID:95975fe0-b4db-0132-1d21-60a44ce903c3
|
||||
Team48Name:NHYC
|
||||
Team48Comp1UUID:95984f80-b4db-0132-1d22-60a44ce903c3
|
||||
|
||||
Team49UUID:94b16cf0-b4db-0132-1cda-60a44ce903c3
|
||||
Team49Name:Ostsee-Segler-Gemeinschaft
|
||||
|
||||
Team50UUID:ac6bbce0-b377-0132-11ad-60a44ce903c3
|
||||
Team50Name:Peter Funke
|
||||
|
||||
Team51UUID:95357460-b4db-0132-1d06-60a44ce903c3
|
||||
Team51Name:Point Yacht Club
|
||||
Team51Comp1UUID:95368d50-b4db-0132-1d07-60a44ce903c3
|
||||
|
||||
Team52UUID:955fbdc0-b4db-0132-1d11-60a44ce903c3
|
||||
Team52Name:Point YC
|
||||
Team52Comp1UUID:956a8030-b4db-0132-1d12-60a44ce903c3
|
||||
|
||||
Team53UUID:947bc310-b4db-0132-1cc6-60a44ce903c3
|
||||
Team53Name:Port Kembla SC
|
||||
Team53Comp1UUID:947c7c10-b4db-0132-1cc7-60a44ce903c3
|
||||
|
||||
Team54UUID:950350e0-b4db-0132-1cf2-60a44ce903c3
|
||||
Team54Name:Pretoria Sailing Club
|
||||
Team54Comp1UUID:95044100-b4db-0132-1cf3-60a44ce903c3
|
||||
|
||||
Team55UUID:9575e320-b4db-0132-1d15-60a44ce903c3
|
||||
Team55Name:Pretoria SC
|
||||
|
||||
Team56UUID:95226ee0-b4db-0132-1cfe-60a44ce903c3
|
||||
Team56Name:PSC
|
||||
Team56Comp1UUID:45750710-b6e4-0132-2282-60a44ce903c3
|
||||
Team56Comp2UUID:2a9a8a70-b689-0132-2213-60a44ce903c3
|
||||
Team56Comp3UUID:952360a0-b4db-0132-1cff-60a44ce903c3
|
||||
|
||||
Team57UUID:957fedb0-b4db-0132-1d19-60a44ce903c3
|
||||
Team57Name:RCYC
|
||||
Team57Comp1UUID:9580e450-b4db-0132-1d1a-60a44ce903c3
|
||||
|
||||
Team58UUID:ac7360e0-b377-0132-11c3-60a44ce903c3
|
||||
Team58Name:Ricky Robinson
|
||||
|
||||
Team59UUID:f1a55510-b500-0132-1e66-60a44ce903c3
|
||||
Team59Name:Rock Sailing Club
|
||||
Team59Comp1UUID:f1a864c0-b500-0132-1e67-60a44ce903c3
|
||||
|
||||
Team60UUID:ac653090-b377-0132-119f-60a44ce903c3
|
||||
Team60Name:Rodger Deane
|
||||
|
||||
Team61UUID:946600a0-b4db-0132-1cbe-60a44ce903c3
|
||||
Team61Name:Royal Brighton Yacht Club
|
||||
Team61Comp1UUID:946739e0-b4db-0132-1cbf-60a44ce903c3
|
||||
|
||||
Team62UUID:950e5de0-b4db-0132-1cf6-60a44ce903c3
|
||||
Team62Name:Royal Cape Yacht Club
|
||||
Team62Comp1UUID:950fd6e0-b4db-0132-1cf7-60a44ce903c3
|
||||
|
||||
Team63UUID:952ba070-b4db-0132-1d02-60a44ce903c3
|
||||
Team63Name:Royal Natal Yacht Club
|
||||
Team63Comp1UUID:952c9010-b4db-0132-1d03-60a44ce903c3
|
||||
|
||||
Team64UUID:9445a600-b4db-0132-1cba-60a44ce903c3
|
||||
Team64Name:RPEYC
|
||||
Team64Comp1UUID:944a7e00-b4db-0132-1cbb-60a44ce903c3
|
||||
|
||||
Team65UUID:ac6312c0-b377-0132-1199-60a44ce903c3
|
||||
Team65Name:Sam Cronin
|
||||
|
||||
Team66UUID:ac63c0c0-b377-0132-119b-60a44ce903c3
|
||||
Team66Name:Sandy HigginsÂ
|
||||
|
||||
Team67UUID:c5a0f950-b377-0132-11d9-60a44ce903c3
|
||||
Team67Name:Sandy Higgins
|
||||
|
||||
Team68UUID:5f647e40-b4fc-0132-1d9d-60a44ce903c3
|
||||
Team68Name:Santa Cruz Yacht Club
|
||||
Team68Comp1UUID:5f8e1890-b4fc-0132-1da6-60a44ce903c3
|
||||
Team68Comp2UUID:5f757d60-b4fc-0132-1d9e-60a44ce903c3
|
||||
|
||||
Team69UUID:949b6070-b4db-0132-1cd2-60a44ce903c3
|
||||
Team69Name:SCLL
|
||||
Team69Comp1UUID:949c2dc0-b4db-0132-1cd3-60a44ce903c3
|
||||
|
||||
Team70UUID:94a5f800-b4db-0132-1cd6-60a44ce903c3
|
||||
Team70Name:Segel-Club Ville
|
||||
Team70Comp1UUID:94a6ea50-b4db-0132-1cd7-60a44ce903c3
|
||||
|
||||
Team71UUID:459b10a0-b6e4-0132-228b-60a44ce903c3
|
||||
Team71Name:SGJ
|
||||
Team71Comp1UUID:94b25480-b4db-0132-1cdb-60a44ce903c3
|
||||
|
||||
Team72UUID:3c5a3440-b5c5-0132-204d-60a44ce903c3
|
||||
Team72Name:Shotley Sailing Club
|
||||
Team72Comp1UUID:f0941b50-b5ec-0132-20fb-60a44ce903c3
|
||||
|
||||
Team73UUID:ac715a70-b377-0132-11bd-60a44ce903c3
|
||||
Team73Name:Sigi Bailes
|
||||
|
||||
Team74UUID:ac69c0c0-b377-0132-11a7-60a44ce903c3
|
||||
Team74Name:Stefan Bohm
|
||||
|
||||
Team75UUID:b8ae7900-b10c-0132-075a-60a44ce903c3
|
||||
Team75Name:team
|
||||
|
||||
Team76UUID:ac65e030-b377-0132-11a1-60a44ce903c3
|
||||
Team76Name:Terry Scutcher
|
||||
|
||||
Team77UUID:453f5410-b6e4-0132-2277-60a44ce903c3
|
||||
Team77Name:USYC
|
||||
Team77Comp1UUID:94eff380-b4db-0132-1ceb-60a44ce903c3
|
||||
|
||||
Team78UUID:94f918c0-b4db-0132-1cee-60a44ce903c3
|
||||
Team78Name:Vogelvlei Yacht Club
|
||||
Team78Comp1UUID:954a24b0-b4db-0132-1d0e-60a44ce903c3
|
||||
Team78Comp2UUID:94fa1150-b4db-0132-1cef-60a44ce903c3
|
||||
|
||||
Team79UUID:953f9670-b4db-0132-1d0a-60a44ce903c3
|
||||
Team79Name: RCYC
|
||||
Team79Comp1UUID:95407f70-b4db-0132-1d0b-60a44ce903c3
|
||||
|
||||
Comp1UUID:944a7e00-b4db-0132-1cbb-60a44ce903c3
|
||||
Comp1Name:Quirk / Payne
|
||||
Comp1NameShort:AUS 9134
|
||||
Comp1Color:FFFF00
|
||||
Comp1Nationality:AUS
|
||||
Comp1Description:Michael Quirk/Luke Payne
|
||||
Comp1Handicap:1.0000000000
|
||||
Comp1HandicapDistance:0.00000
|
||||
Comp1StartTime:2015-04-02 12:40:00
|
||||
Comp1ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp1RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp2UUID:946739e0-b4db-0132-1cbf-60a44ce903c3
|
||||
Comp2Name:Bowden / Bath
|
||||
Comp2NameShort:AUS 9018
|
||||
Comp2Color:0000FF
|
||||
Comp2Nationality:AUS
|
||||
Comp2Description:Brett Bowden/Robert Bath
|
||||
Comp2Handicap:1.0000000000
|
||||
Comp2HandicapDistance:0.00000
|
||||
Comp2StartTime:2015-04-02 12:40:00
|
||||
Comp2ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp2RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp3UUID:9471bf20-b4db-0132-1cc3-60a44ce903c3
|
||||
Comp3Name:Higgins / Johnston
|
||||
Comp3NameShort:AUS 8890
|
||||
Comp3Color:008000
|
||||
Comp3Nationality:AUS
|
||||
Comp3Description:Malcolm Higgins/Nick Johnston
|
||||
Comp3Handicap:1.0000000000
|
||||
Comp3HandicapDistance:0.00000
|
||||
Comp3StartTime:2015-04-02 12:40:00
|
||||
Comp3ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp3RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp4UUID:947c7c10-b4db-0132-1cc7-60a44ce903c3
|
||||
Comp4Name:Cronin / Whitbread
|
||||
Comp4NameShort:AUS 8637
|
||||
Comp4Color:000000
|
||||
Comp4Nationality:AUS
|
||||
Comp4Description:Sam Cronin/Kevin Whitbread
|
||||
Comp4Handicap:1.0000000000
|
||||
Comp4HandicapDistance:0.00000
|
||||
Comp4StartTime:2015-04-02 12:40:00
|
||||
Comp4ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp4RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp5UUID:94865d50-b4db-0132-1ccb-60a44ce903c3
|
||||
Comp5Name:Higgins / Marsh
|
||||
Comp5NameShort:AUS 8946
|
||||
Comp5Color:008000
|
||||
Comp5Nationality:AUS
|
||||
Comp5Description:Sandy Higgins /Paul Marsh
|
||||
Comp5Handicap:1.0000000000
|
||||
Comp5HandicapDistance:0.00000
|
||||
Comp5StartTime:2015-04-02 12:40:00
|
||||
Comp5ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp5RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp6UUID:949331b0-b4db-0132-1ccf-60a44ce903c3
|
||||
Comp6Name:Pinnell / Tellen
|
||||
Comp6NameShort:GBR 9136
|
||||
Comp6Color:800080
|
||||
Comp6Nationality:GBR
|
||||
Comp6Description:Ian Pinnell/Johannes Tellen
|
||||
Comp6Handicap:1.0000000000
|
||||
Comp6HandicapDistance:0.00000
|
||||
Comp6StartTime:2015-04-02 12:40:00
|
||||
Comp6ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp6RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp7UUID:949c2dc0-b4db-0132-1cd3-60a44ce903c3
|
||||
Comp7Name:Mittermayer / Barteldt
|
||||
Comp7NameShort:GER 8988
|
||||
Comp7Color:800080
|
||||
Comp7Nationality:GER
|
||||
Comp7Description:Georg Mittermayer/Dirk Barteldt
|
||||
Comp7Handicap:1.0000000000
|
||||
Comp7HandicapDistance:0.00000
|
||||
Comp7StartTime:2015-04-02 12:40:00
|
||||
Comp7ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp7RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp8UUID:94a6ea50-b4db-0132-1cd7-60a44ce903c3
|
||||
Comp8Name:Bohm / Roos
|
||||
Comp8NameShort:GER 9135
|
||||
Comp8Color:008000
|
||||
Comp8Nationality:GER
|
||||
Comp8Description:Stefan Bohm/Gerald Roos
|
||||
Comp8Handicap:1.0000000000
|
||||
Comp8HandicapDistance:0.00000
|
||||
Comp8StartTime:2015-04-02 12:40:00
|
||||
Comp8ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp8RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp9UUID:94b25480-b4db-0132-1cdb-60a44ce903c3
|
||||
Comp9Name:Menge / Wilts
|
||||
Comp9NameShort:GER 9000
|
||||
Comp9Color:808000
|
||||
Comp9Nationality:GER
|
||||
Comp9Description:Katharina Menge/Enno Wilts
|
||||
Comp9Handicap:1.0000000000
|
||||
Comp9HandicapDistance:0.00000
|
||||
Comp9StartTime:2015-04-02 12:40:00
|
||||
Comp9ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp9RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp10UUID:94c44e80-b4db-0132-1cdf-60a44ce903c3
|
||||
Comp10Name:Stenger / Birkner
|
||||
Comp10NameShort:GER 9043
|
||||
Comp10Color:008080
|
||||
Comp10Nationality:GER
|
||||
Comp10Description:Angela Stenger/Nicola Birkner
|
||||
Comp10Handicap:1.0000000000
|
||||
Comp10HandicapDistance:0.00000
|
||||
Comp10StartTime:2015-04-02 12:40:00
|
||||
Comp10ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp10RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp11UUID:94d00ef0-b4db-0132-1ce3-60a44ce903c3
|
||||
Comp11Name:Funke / Funke
|
||||
Comp11NameShort:RSA 9094
|
||||
Comp11Color:191970
|
||||
Comp11Nationality:RSA
|
||||
Comp11Description:Peter Funke/Thomas Funke
|
||||
Comp11Handicap:1.0000000000
|
||||
Comp11HandicapDistance:0.00000
|
||||
Comp11StartTime:2015-04-02 12:40:00
|
||||
Comp11ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp11RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp12UUID:94e60210-b4db-0132-1ce7-60a44ce903c3
|
||||
Comp12Name:Arthur / Anderson
|
||||
Comp12NameShort:RSA 6266
|
||||
Comp12Color:FFFF00
|
||||
Comp12Nationality:RSA
|
||||
Comp12Description:Andrew Arthur/John Anderson
|
||||
Comp12Handicap:1.0000000000
|
||||
Comp12HandicapDistance:0.00000
|
||||
Comp12StartTime:2015-04-02 12:40:00
|
||||
Comp12ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp12RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp13UUID:94eff380-b4db-0132-1ceb-60a44ce903c3
|
||||
Comp13Name:Ham / Ham
|
||||
Comp13NameShort:RSA 8719
|
||||
Comp13Color:FF0000
|
||||
Comp13Nationality:RSA
|
||||
Comp13Description:Alexander Ham/Warwick Ham
|
||||
Comp13Handicap:1.0000000000
|
||||
Comp13HandicapDistance:0.00000
|
||||
Comp13StartTime:2015-04-02 12:40:00
|
||||
Comp13ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp13RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp14UUID:94fa1150-b4db-0132-1cef-60a44ce903c3
|
||||
Comp14Name:Snyman / Ballantyne
|
||||
Comp14NameShort:RSA 8091
|
||||
Comp14Color:800000
|
||||
Comp14Nationality:RSA
|
||||
Comp14Description:Gideon Snyman/Courtney Ballantyne
|
||||
Comp14Handicap:1.0000000000
|
||||
Comp14HandicapDistance:0.00000
|
||||
Comp14StartTime:2015-04-02 12:40:00
|
||||
Comp14ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp14RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp15UUID:95044100-b4db-0132-1cf3-60a44ce903c3
|
||||
Comp15Name:Holm / Fourie
|
||||
Comp15NameShort:RSA 8707
|
||||
Comp15Color:191970
|
||||
Comp15Nationality:RSA
|
||||
Comp15Description:Albrecht Holm/Marius Fourie
|
||||
Comp15Handicap:1.0000000000
|
||||
Comp15HandicapDistance:0.00000
|
||||
Comp15StartTime:2015-04-02 12:40:00
|
||||
Comp15ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp15RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp16UUID:950fd6e0-b4db-0132-1cf7-60a44ce903c3
|
||||
Comp16Name:Largier / Hutton-Squire
|
||||
Comp16NameShort:RSA 9063
|
||||
Comp16Color:008080
|
||||
Comp16Nationality:RSA
|
||||
Comp16Description:James Largier/Richard Hutton-Squire
|
||||
Comp16Handicap:1.0000000000
|
||||
Comp16HandicapDistance:0.00000
|
||||
Comp16StartTime:2015-04-02 12:40:00
|
||||
Comp16ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp16RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp17UUID:951a3330-b4db-0132-1cfb-60a44ce903c3
|
||||
Comp17Name:Bailes / Isaac
|
||||
Comp17NameShort:RSA 7608
|
||||
Comp17Color:808000
|
||||
Comp17Nationality:RSA
|
||||
Comp17Description:Sigi Bailes/Dudley Isaac
|
||||
Comp17Handicap:1.0000000000
|
||||
Comp17HandicapDistance:0.00000
|
||||
Comp17StartTime:2015-04-02 12:40:00
|
||||
Comp17ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp17RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp18UUID:952360a0-b4db-0132-1cff-60a44ce903c3
|
||||
Comp18Name:Ham / Robinson
|
||||
Comp18NameShort:RSA 8789
|
||||
Comp18Color:FFFF00
|
||||
Comp18Nationality:GBR
|
||||
Comp18Description:Jonathan Ham / Michaela Robinson
|
||||
Comp18Handicap:1.0000000000
|
||||
Comp18HandicapDistance:0.00000
|
||||
Comp18StartTime:2015-04-02 12:40:00
|
||||
Comp18ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp18RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp19UUID:952c9010-b4db-0132-1d03-60a44ce903c3
|
||||
Comp19Name:Robinson / Robinson
|
||||
Comp19NameShort:RSA 8486
|
||||
Comp19Color:800000
|
||||
Comp19Nationality:RSA
|
||||
Comp19Description:Ricky Robinson/Brennan Robinson
|
||||
Comp19Handicap:1.0000000000
|
||||
Comp19HandicapDistance:0.00000
|
||||
Comp19StartTime:2015-04-02 12:40:00
|
||||
Comp19ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp19RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp20UUID:95368d50-b4db-0132-1d07-60a44ce903c3
|
||||
Comp20Name:Hurter / May
|
||||
Comp20NameShort:RSA 8675
|
||||
Comp20Color:0000FF
|
||||
Comp20Nationality:RSA
|
||||
Comp20Description:Euan Hurter/Murray May
|
||||
Comp20Handicap:1.0000000000
|
||||
Comp20HandicapDistance:0.00000
|
||||
Comp20StartTime:2015-04-02 12:40:00
|
||||
Comp20ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp20RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp21UUID:95407f70-b4db-0132-1d0b-60a44ce903c3
|
||||
Comp21Name:Plattner / Shelton
|
||||
Comp21NameShort:RSA 9026
|
||||
Comp21Color:800080
|
||||
Comp21Nationality:RSA
|
||||
Comp21Description:Tina Plattner/David Shelton
|
||||
Comp21Handicap:1.0000000000
|
||||
Comp21HandicapDistance:0.00000
|
||||
Comp21StartTime:2015-04-02 12:40:00
|
||||
Comp21ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp21RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp22UUID:954a24b0-b4db-0132-1d0e-60a44ce903c3
|
||||
Comp22Name:Ballantyne / Groenewald
|
||||
Comp22NameShort:RSA 4437
|
||||
Comp22Color:FF8C00
|
||||
Comp22Nationality:RSA
|
||||
Comp22Description:Grant Ballantyne/Evert Groenewald
|
||||
Comp22Handicap:1.0000000000
|
||||
Comp22HandicapDistance:0.00000
|
||||
Comp22StartTime:2015-04-02 12:40:00
|
||||
Comp22ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp22RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp23UUID:956a8030-b4db-0132-1d12-60a44ce903c3
|
||||
Comp23Name:Rogotzki / Roos
|
||||
Comp23NameShort:RSA 8584
|
||||
Comp23Color:FF8C00
|
||||
Comp23Nationality:RSA
|
||||
Comp23Description:Hans Rogotzki/Peter Roos
|
||||
Comp23Handicap:1.0000000000
|
||||
Comp23HandicapDistance:0.00000
|
||||
Comp23StartTime:2015-04-02 12:40:00
|
||||
Comp23ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp23RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp24UUID:9580e450-b4db-0132-1d1a-60a44ce903c3
|
||||
Comp24Name:D'Engle / Nel
|
||||
Comp24NameShort:RSA 8520
|
||||
Comp24Color:00FF00
|
||||
Comp24Nationality:RSA
|
||||
Comp24Description:Jay D'Engle /De-Wet Nel
|
||||
Comp24Handicap:1.0000000000
|
||||
Comp24HandicapDistance:0.00000
|
||||
Comp24StartTime:2015-04-02 12:40:00
|
||||
Comp24ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp24RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp25UUID:958bd200-b4db-0132-1d1e-60a44ce903c3
|
||||
Comp25Name:Ross / Waterman
|
||||
Comp25NameShort:USA 8084
|
||||
Comp25Color:FF00FF
|
||||
Comp25Nationality:USA
|
||||
Comp25Description:Aaron Ross/Rob Waterman
|
||||
Comp25Handicap:1.0000000000
|
||||
Comp25HandicapDistance:0.00000
|
||||
Comp25StartTime:2015-04-02 12:40:00
|
||||
Comp25ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp25RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp26UUID:95984f80-b4db-0132-1d22-60a44ce903c3
|
||||
Comp26Name:Hamlin / Nelson
|
||||
Comp26NameShort:USA 9153
|
||||
Comp26Color:800000
|
||||
Comp26Nationality:USA
|
||||
Comp26Description:Howard Hamlin / Jeff Nelson
|
||||
Comp26Handicap:1.0000000000
|
||||
Comp26HandicapDistance:0.00000
|
||||
Comp26StartTime:2015-04-02 12:40:00
|
||||
Comp26ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp26RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp27UUID:5f757d60-b4fc-0132-1d9e-60a44ce903c3
|
||||
Comp27Name:Holt / Smit
|
||||
Comp27NameShort:USA 9072
|
||||
Comp27Color:00FF00
|
||||
Comp27Nationality:USA
|
||||
Comp27Description:Mike Holt/Carl Smit
|
||||
Comp27Handicap:1.0000000000
|
||||
Comp27HandicapDistance:0.00000
|
||||
Comp27StartTime:2015-04-02 12:40:00
|
||||
Comp27ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp27RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp28UUID:5f853dc0-b4fc-0132-1da2-60a44ce903c3
|
||||
Comp28Name:Saugmann / Karbo
|
||||
Comp28NameShort:DEN 9118
|
||||
Comp28Color:008080
|
||||
Comp28Nationality:DEN
|
||||
Comp28Description:Jan Saugmann/Jakob Karbo
|
||||
Comp28Handicap:1.0000000000
|
||||
Comp28HandicapDistance:0.00000
|
||||
Comp28StartTime:2015-04-02 12:40:00
|
||||
Comp28ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp28RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp29UUID:5f8e1890-b4fc-0132-1da6-60a44ce903c3
|
||||
Comp29Name:Conrads / Haines
|
||||
Comp29NameShort:USA 9091
|
||||
Comp29Color:00FF00
|
||||
Comp29Nationality:USA
|
||||
Comp29Description:Ted Conrads/Brian Haines
|
||||
Comp29Handicap:1.0000000000
|
||||
Comp29HandicapDistance:0.00000
|
||||
Comp29StartTime:2015-04-02 12:40:00
|
||||
Comp29ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp29RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp30UUID:f1a864c0-b500-0132-1e67-60a44ce903c3
|
||||
Comp30Name:Deane / Deane
|
||||
Comp30NameShort:GBR 9125
|
||||
Comp30Color:191970
|
||||
Comp30Nationality:GBR
|
||||
Comp30Description:Roger Deane/Nigel Deane
|
||||
Comp30Handicap:1.0000000000
|
||||
Comp30HandicapDistance:0.00000
|
||||
Comp30StartTime:2015-04-02 12:40:00
|
||||
Comp30ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp30RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp31UUID:3c51d930-b5c5-0132-204a-60a44ce903c3
|
||||
Comp31Name:Parker / Holm
|
||||
Comp31NameShort:RSA 8948
|
||||
Comp31Color:FF00FF
|
||||
Comp31Nationality:RSA
|
||||
Comp31Description:Anthony Parker / Dietmar Holm
|
||||
Comp31Handicap:1.0000000000
|
||||
Comp31HandicapDistance:0.00000
|
||||
Comp31StartTime:2015-04-02 12:40:00
|
||||
Comp31ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp31RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp32UUID:f0941b50-b5ec-0132-20fb-60a44ce903c3
|
||||
Comp32Name:Scutcher / Diebitsch
|
||||
Comp32NameShort:GBR 9124
|
||||
Comp32Color:FF0000
|
||||
Comp32Nationality:GBR
|
||||
Comp32Description:Terry Scutcher/Christian Diebitsch
|
||||
Comp32Handicap:1.0000000000
|
||||
Comp32HandicapDistance:0.00000
|
||||
Comp32StartTime:2015-04-02 12:40:00
|
||||
Comp32ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp32RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp33UUID:2a9a8a70-b689-0132-2213-60a44ce903c3
|
||||
Comp33Name:Klaas / Tellen
|
||||
Comp33NameShort:RSA 7822
|
||||
Comp33Color:000000
|
||||
Comp33Nationality:RSA
|
||||
Comp33Description:Bronwen Klaas /Aron Tellen
|
||||
Comp33Handicap:1.0000000000
|
||||
Comp33HandicapDistance:0.00000
|
||||
Comp33StartTime:2015-04-02 12:40:00
|
||||
Comp33ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp33RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp34UUID:4568fe80-b6e4-0132-227f-60a44ce903c3
|
||||
Comp34Name:Holtshauzen / Kirchner
|
||||
Comp34NameShort:RSA 8121
|
||||
Comp34Color:0000FF
|
||||
Comp34Nationality:RSA
|
||||
Comp34Description:Kobus Holtshauzen /Danie Kirchner
|
||||
Comp34Handicap:1.0000000000
|
||||
Comp34HandicapDistance:0.00000
|
||||
Comp34StartTime:2015-04-02 12:40:00
|
||||
Comp34ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp34RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp35UUID:45750710-b6e4-0132-2282-60a44ce903c3
|
||||
Comp35Name:Klaas / Von Gruenewaldt
|
||||
Comp35NameShort:RSA 8445
|
||||
Comp35Color:FF8C00
|
||||
Comp35Nationality:RSA
|
||||
Comp35Description:Kyle Klaas/Robert Von Gruenewaldt
|
||||
Comp35Handicap:1.0000000000
|
||||
Comp35HandicapDistance:0.00000
|
||||
Comp35StartTime:2015-04-02 12:40:00
|
||||
Comp35ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp35RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
Comp36UUID:45b290d0-b6e4-0132-2291-60a44ce903c3
|
||||
Comp36Name:Foreman / Van Der Ploeg
|
||||
Comp36NameShort:RSA 8537
|
||||
Comp36Color:808000
|
||||
Comp36Nationality:RSA
|
||||
Comp36Description:Kevin Foreman/Adam Van Der Ploeg
|
||||
Comp36Handicap:1.0000000000
|
||||
Comp36HandicapDistance:0.00000
|
||||
Comp36StartTime:2015-04-02 12:40:00
|
||||
Comp36ClassUUID:ac5ab750-b377-0132-1193-60a44ce903c3
|
||||
Comp36RouteUUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
|
||||
ControlPoint1UUID:25f25a90-b906-0132-2954-60a44ce903c3
|
||||
ControlPoint1Name:Windward-F-30
|
||||
ControlPoint1HasTwoPoints:0
|
||||
ControlPoint1ShortName:1R
|
||||
ControlPoint1Mark1Lon:25.694404
|
||||
ControlPoint1Mark1Lat:-33.957179
|
||||
ControlPoint1Mark1Static:1
|
||||
|
||||
ControlPoint2UUID:26126690-b4de-0132-1d39-60a44ce903c3
|
||||
ControlPoint2Name:Replacement mark
|
||||
ControlPoint2HasTwoPoints:0
|
||||
ControlPoint2ShortName:Re
|
||||
ControlPoint2Mark1Lon:25.687969
|
||||
ControlPoint2Mark1Lat:-33.949951
|
||||
ControlPoint2Mark1Static:0
|
||||
|
||||
ControlPoint3UUID:3fe5f360-b52a-0132-1f57-60a44ce903c3
|
||||
ControlPoint3Name:Leeward gate PreW
|
||||
ControlPoint3HasTwoPoints:1
|
||||
ControlPoint3ShortName:3
|
||||
ControlPoint3Mark1Lon:25.633739
|
||||
ControlPoint3Mark1Lat:-33.967300
|
||||
ControlPoint3Mark1Static:0
|
||||
ControlPoint3Mark2Lon:25.633644
|
||||
ControlPoint3Mark2Lat:-33.967327
|
||||
ControlPoint3Mark2Static:0
|
||||
|
||||
ControlPoint4UUID:69c13f40-b37c-0132-1211-60a44ce903c3
|
||||
ControlPoint4Name:Wing
|
||||
ControlPoint4HasTwoPoints:0
|
||||
ControlPoint4ShortName:2
|
||||
ControlPoint4Mark1Lon:25.689110
|
||||
ControlPoint4Mark1Lat:-33.970020
|
||||
ControlPoint4Mark1Static:0
|
||||
|
||||
ControlPoint5UUID:8f54b350-b767-0132-2455-60a44ce903c3
|
||||
ControlPoint5Name:3 Worlds 1
|
||||
ControlPoint5HasTwoPoints:1
|
||||
ControlPoint5ShortName:3
|
||||
ControlPoint5Mark1Lon:25.665367
|
||||
ControlPoint5Mark1Lat:-33.961881
|
||||
ControlPoint5Mark1Static:1
|
||||
ControlPoint5Mark2Lon:25.665457
|
||||
ControlPoint5Mark2Lat:-33.962330
|
||||
ControlPoint5Mark2Static:1
|
||||
|
||||
ControlPoint6UUID:9ca100f0-b68c-0132-2216-60a44ce903c3
|
||||
ControlPoint6Name:3p
|
||||
ControlPoint6HasTwoPoints:0
|
||||
ControlPoint6ShortName:3p
|
||||
ControlPoint6Mark1Lon:25.683989
|
||||
ControlPoint6Mark1Lat:-33.954403
|
||||
ControlPoint6Mark1Static:0
|
||||
|
||||
ControlPoint7UUID:ab8077c0-b37c-0132-1218-60a44ce903c3
|
||||
ControlPoint7Name:Finish
|
||||
ControlPoint7HasTwoPoints:1
|
||||
ControlPoint7ShortName:F
|
||||
ControlPoint7Mark1Lon:25.687019
|
||||
ControlPoint7Mark1Lat:-33.950874
|
||||
ControlPoint7Mark1Static:0
|
||||
ControlPoint7Mark2Lon:25.688251
|
||||
ControlPoint7Mark2Lat:-33.950226
|
||||
ControlPoint7Mark2Static:0
|
||||
|
||||
ControlPoint8UUID:af1bb4f0-b68c-0132-2219-60a44ce903c3
|
||||
ControlPoint8Name:3s
|
||||
ControlPoint8HasTwoPoints:0
|
||||
ControlPoint8ShortName:3S
|
||||
ControlPoint8Mark1Lon:25.684313
|
||||
ControlPoint8Mark1Lat:-33.954788
|
||||
ControlPoint8Mark1Static:0
|
||||
|
||||
ControlPoint9UUID:b10792d0-b5e2-0132-20f9-60a44ce903c3
|
||||
ControlPoint9Name:Fin R2
|
||||
ControlPoint9HasTwoPoints:1
|
||||
ControlPoint9ShortName:Fi
|
||||
ControlPoint9Mark1Lon:25.724152
|
||||
ControlPoint9Mark1Lat:-34.001356
|
||||
ControlPoint9Mark1Static:1
|
||||
ControlPoint9Mark2Lon:25.723793
|
||||
ControlPoint9Mark2Lat:-34.001524
|
||||
ControlPoint9Mark2Static:1
|
||||
|
||||
ControlPoint10UUID:cf64f6d0-b4dd-0132-1d2c-60a44ce903c3
|
||||
ControlPoint10Name:Start
|
||||
ControlPoint10HasTwoPoints:1
|
||||
ControlPoint10ShortName:St
|
||||
ControlPoint10Mark1Lon:25.686090
|
||||
ControlPoint10Mark1Lat:-33.950943
|
||||
ControlPoint10Mark1Static:0
|
||||
ControlPoint10Mark2Lon:25.687042
|
||||
ControlPoint10Mark2Lat:-33.950867
|
||||
ControlPoint10Mark2Static:0
|
||||
|
||||
ControlPoint11UUID:d742d1b0-b68c-0132-221c-60a44ce903c3
|
||||
ControlPoint11Name:Leeward gate Worlds
|
||||
ControlPoint11HasTwoPoints:1
|
||||
ControlPoint11ShortName:3
|
||||
ControlPoint11DataSheet:P1.UUID=af1bb4f0-b68c-0132-2219-60a44ce903c3###BREAKLINE###P2.UUID=9ca100f0-b68c-0132-2216-60a44ce903c3###BREAKLINE###
|
||||
ControlPoint11Mark1Lon:25.684313
|
||||
ControlPoint11Mark1Lat:-33.954788
|
||||
ControlPoint11Mark1Static:0
|
||||
ControlPoint11Mark2Lon:25.683989
|
||||
ControlPoint11Mark2Lat:-33.954403
|
||||
ControlPoint11Mark2Static:0
|
||||
|
||||
ControlPoint12UUID:e6879b90-b4dd-0132-1d30-60a44ce903c3
|
||||
ControlPoint12Name:Windward
|
||||
ControlPoint12HasTwoPoints:0
|
||||
ControlPoint12ShortName:1
|
||||
ControlPoint12Mark1Lon:25.668341
|
||||
ControlPoint12Mark1Lat:-33.968086
|
||||
ControlPoint12Mark1Static:0
|
||||
|
||||
ControlPoint13UUID:f6f60d80-b4dd-0132-1d33-60a44ce903c3
|
||||
ControlPoint13Name:Spreader
|
||||
ControlPoint13HasTwoPoints:0
|
||||
ControlPoint13ShortName:1a
|
||||
ControlPoint13Mark1Lon:25.669888
|
||||
ControlPoint13Mark1Lat:-33.968563
|
||||
ControlPoint13Mark1Static:0
|
||||
|
||||
SegmentFile:segments.php
|
||||
Route1UUID:66ccabb0-b77d-0132-26e9-60a44ce903c3
|
||||
Route1Description:Worlds 7
|
||||
Route1Length:0
|
||||
Route1ControlPoint1UUID:cf64f6d0-b4dd-0132-1d2c-60a44ce903c3
|
||||
Route1ControlPoint2UUID:e6879b90-b4dd-0132-1d30-60a44ce903c3
|
||||
Route1ControlPoint3UUID:f6f60d80-b4dd-0132-1d33-60a44ce903c3
|
||||
Route1ControlPoint4UUID:d742d1b0-b68c-0132-221c-60a44ce903c3
|
||||
Route1ControlPoint5UUID:e6879b90-b4dd-0132-1d30-60a44ce903c3
|
||||
Route1ControlPoint6UUID:f6f60d80-b4dd-0132-1d33-60a44ce903c3
|
||||
Route1ControlPoint7UUID:69c13f40-b37c-0132-1211-60a44ce903c3
|
||||
Route1ControlPoint8UUID:d742d1b0-b68c-0132-221c-60a44ce903c3
|
||||
Route1ControlPoint9UUID:e6879b90-b4dd-0132-1d30-60a44ce903c3
|
||||
Route1ControlPoint10UUID:f6f60d80-b4dd-0132-1d33-60a44ce903c3
|
||||
Route1ControlPoint11UUID:d742d1b0-b68c-0132-221c-60a44ce903c3
|
||||
Route1ControlPoint12UUID:ab8077c0-b37c-0132-1218-60a44ce903c3
|
||||
|
||||
//-------- AUTO GENERATED PARAMETERS END ------------------
|
||||
+3
@@ -15,6 +15,9 @@ import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
public abstract class AbstractManeuverDetectionTestCase extends OnlineTracTracBasedTest {
|
||||
/**
|
||||
* A date parser for UTC+2 in format MM/dd/yyyy-HH:mm:ss.
|
||||
*/
|
||||
protected SimpleDateFormat dateFormat;
|
||||
protected static final int TACK_TOLERANCE = 7000;
|
||||
protected static final int JIBE_TOLERANCE = 7000;
|
||||
|
||||
+11
@@ -5,8 +5,10 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -42,4 +44,13 @@ public class ArrayListNavigableSetTest {
|
||||
Integer integerFromSet = hashSet.iterator().next();
|
||||
assertSame(i1, integerFromSet); // supposedly not replaced
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiAdd() {
|
||||
Set<Integer> arrayListNavigableSet = new ArrayListNavigableSet<Integer>(Comparator.naturalOrder());
|
||||
final List<Integer> elements = Arrays.asList(1, 2, 3, 4, 5);
|
||||
arrayListNavigableSet.addAll(elements);
|
||||
assertEquals(5, arrayListNavigableSet.size());
|
||||
assertEquals(new HashSet<>(elements), arrayListNavigableSet);
|
||||
}
|
||||
}
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.CourseArea;
|
||||
import com.sap.sailing.domain.base.impl.CourseAreaImpl;
|
||||
import com.sap.sailing.domain.common.sensordata.BravoExtendedSensorDataMetadata;
|
||||
import com.sap.sailing.domain.common.tracking.impl.BravoExtendedFixImpl;
|
||||
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
|
||||
import com.sap.sailing.domain.tracking.DynamicBravoFixTrack;
|
||||
import com.sap.sailing.domain.tracking.impl.BravoFixTrackImpl;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
public class BravoFixTrackAverageRideHeightTest {
|
||||
private DynamicBravoFixTrack<CourseArea> track;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
track = new BravoFixTrackImpl<>(new CourseAreaImpl("Test", UUID.randomUUID()),
|
||||
"test", /* hasExtendedFixes */ true);
|
||||
track.add(createFix(10l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.));
|
||||
track.add(createFix(20l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.));
|
||||
track.add(createFix(30l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAverageRideHeightCachingForEmptyInterval() {
|
||||
assertNull(track.getAverageRideHeight(t(11l), t(19l)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAverageRideHeightCachingForNonEmptyInterval() {
|
||||
assertEquals(0.6, track.getAverageRideHeight(t(11l), t(21l)).getMeters(), 0.00001); // make sure that the cached "null" result doesn't offend surrounding calculations
|
||||
assertEquals(0.6, track.getAverageRideHeight(t(9l), t(21l)).getMeters(), 0.00001); // also when the first fix is included
|
||||
assertEquals(0.6, track.getAverageRideHeight(t(9l), t(31l)).getMeters(), 0.00001); // ensure that results are still correct when cache intervals are "added"
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAverageRideHeightCachingForNonEmptyIntervalAfterHavingCachedEmptyIntervalResult() {
|
||||
testAverageRideHeightCachingForEmptyInterval();
|
||||
assertEquals(0.6, track.getAverageRideHeight(t(11l), t(21l)).getMeters(), 0.00001); // make sure that the cached "null" result doesn't offend surrounding calculations
|
||||
assertEquals(0.6, track.getAverageRideHeight(t(9l), t(21l)).getMeters(), 0.00001); // also when the first fix is included
|
||||
assertEquals(0.6, track.getAverageRideHeight(t(9l), t(31l)).getMeters(), 0.00001); // ensure that results are still correct when cache intervals are "added"
|
||||
}
|
||||
|
||||
private BravoExtendedFixImpl createFix(long timePointAsMillis, Double rideHeightPort, Double rideHeightStarboard, Double heel, Double pitch) {
|
||||
final Double[] fixData = new Double[Collections.max(Arrays.asList(
|
||||
BravoExtendedSensorDataMetadata.HEEL.getColumnIndex()+1,
|
||||
BravoExtendedSensorDataMetadata.PITCH.getColumnIndex()+1,
|
||||
BravoExtendedSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()+1,
|
||||
BravoExtendedSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()+1))];
|
||||
fixData[BravoExtendedSensorDataMetadata.HEEL.getColumnIndex()] = heel;
|
||||
fixData[BravoExtendedSensorDataMetadata.PITCH.getColumnIndex()] = pitch;
|
||||
fixData[BravoExtendedSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()] = rideHeightPort;
|
||||
fixData[BravoExtendedSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()] = rideHeightStarboard;
|
||||
return new BravoExtendedFixImpl(new DoubleVectorFixImpl(t(timePointAsMillis), fixData));
|
||||
}
|
||||
|
||||
private MillisecondsTimePoint t(long timePointAsMillis) {
|
||||
return new MillisecondsTimePoint(timePointAsMillis);
|
||||
}
|
||||
}
|
||||
+5
@@ -47,6 +47,11 @@ public class DummyTrackedRegattaRegistry implements TrackedRegattaRegistry {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRace(Regatta regatta, RaceDefinition race)
|
||||
throws MalformedURLException, IOException, InterruptedException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRaceBeingTracked(Regatta regattaContext, RaceDefinition r) {
|
||||
return false;
|
||||
|
||||
+4
@@ -145,6 +145,10 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
|
||||
public void stopTracking(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,
|
||||
InterruptedException {
|
||||
}
|
||||
@Override
|
||||
public void removeRace(Regatta regatta, RaceDefinition race)
|
||||
throws MalformedURLException, IOException, InterruptedException {
|
||||
}
|
||||
};
|
||||
LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false);
|
||||
assertNotNull(leaderboardDTO);
|
||||
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
|
||||
import com.sap.sailing.domain.tractracadapter.ReceiverType;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
import com.tractrac.model.lib.api.event.CreateModelException;
|
||||
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
|
||||
|
||||
/**
|
||||
* At the 505 Worlds 2015 Race R7 Arthur/Anderson (RSA 6266) have been tracked with a wild outlier
|
||||
* around 2015-04-02T15:05:07+0200.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class OutlierDetectionTest extends AbstractManeuverDetectionTestCase {
|
||||
public OutlierDetectionTest() throws MalformedURLException, URISyntaxException {
|
||||
super();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws URISyntaxException, IOException, InterruptedException, ParseException, SubscriberInitializationException, CreateModelException {
|
||||
super.setUp();
|
||||
URI storedUri = new URI("file:///"+new File("resources/event_20150323_Worlds-505_Worlds_7.mtb").getCanonicalPath().replace('\\', '/'));
|
||||
super.setUp(new URL("file:///"+new File("resources/event_20150323_Worlds-505_Worlds_7.txt").getCanonicalPath()),
|
||||
/* liveUri */ null, /* storedUri */ storedUri,
|
||||
new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.RACECOURSE, ReceiverType.RAWPOSITIONS });
|
||||
}
|
||||
|
||||
protected String getExpectedEventName() {
|
||||
return "505 Worlds 2015";
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the 505 Race 7 for competitor "Arthur / Anderson" at a time where an outlier exists
|
||||
*/
|
||||
@Test
|
||||
public void testManeuversForFindelCriticalDetection() throws ParseException, NoWindException {
|
||||
Competitor competitor = getCompetitorByName("Arthur / Anderson");
|
||||
assertNotNull(competitor);
|
||||
Date fromDate = dateFormat.parse("04/02/2015-15:04:50");
|
||||
TimePoint from = new MillisecondsTimePoint(fromDate);
|
||||
Date toDate = dateFormat.parse("04/02/2015-15:06:20");
|
||||
TimePoint to = new MillisecondsTimePoint(toDate);
|
||||
assertNotNull(fromDate);
|
||||
assertNotNull(toDate);
|
||||
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = getTrackedRace().getTrack(competitor);
|
||||
track.lockForRead();
|
||||
try {
|
||||
assertTrue(track.getMaximumSpeedOverGround(from, to).getB().getKnots() < 15.);
|
||||
} finally {
|
||||
track.unlockAfterRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -550,6 +550,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
|
||||
public void setControlTrackingFromStartAndFinishTimes(
|
||||
boolean controlTrackingFromStartAndFinishTimes) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFleetsCanRunInParallelToTrue() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ public class MockedTrackedRaceWithFixedRank extends MockedTrackedRace {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boat getBoatOfCompetitorById(Serializable competitorID) {
|
||||
public Boat getBoatOfCompetitor(Competitor competitor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ public class MockedTrackedRaceWithFixedRankAndManyCompetitors extends MockedTrac
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public Boat getBoatOfCompetitorById(Serializable competitorID) {
|
||||
public Boat getBoatOfCompetitor(Competitor competitor) {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
|
||||
+5
-3
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.domain.tractracadapter.persistence.impl;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
@@ -12,6 +13,7 @@ import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
|
||||
import com.sap.sailing.domain.tracking.impl.AbstractRaceTrackingConnectivityParametersHandler;
|
||||
import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
||||
import com.sap.sailing.domain.tractracadapter.impl.RaceTrackingConnectivityParametersImpl;
|
||||
import com.sap.sailing.domain.tractracadapter.impl.TracTracRaceTrackerImpl;
|
||||
import com.sap.sse.common.TypeBasedServiceFinder;
|
||||
import com.sap.sse.common.impl.MillisecondsDurationImpl;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
@@ -52,7 +54,7 @@ public class TracTracConnectivityParamsHandler extends AbstractRaceTrackingConne
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> mapFrom(RaceTrackingConnectivityParameters params) {
|
||||
public Map<String, Object> mapFrom(RaceTrackingConnectivityParameters params) throws MalformedURLException {
|
||||
assert params instanceof RaceTrackingConnectivityParametersImpl;
|
||||
final RaceTrackingConnectivityParametersImpl ttParams = (RaceTrackingConnectivityParametersImpl) params;
|
||||
final Map<String, Object> result = getKey(params);
|
||||
@@ -94,12 +96,12 @@ public class TracTracConnectivityParamsHandler extends AbstractRaceTrackingConne
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getKey(RaceTrackingConnectivityParameters params) {
|
||||
public Map<String, Object> getKey(RaceTrackingConnectivityParameters params) throws MalformedURLException {
|
||||
assert params instanceof RaceTrackingConnectivityParametersImpl;
|
||||
final RaceTrackingConnectivityParametersImpl ttParams = (RaceTrackingConnectivityParametersImpl) params;
|
||||
final Map<String, Object> result = new HashMap<>();
|
||||
result.put(TypeBasedServiceFinder.TYPE, params.getTypeIdentifier());
|
||||
result.put(PARAM_URL, ttParams.getParamURL().toString());
|
||||
result.put(PARAM_URL, TracTracRaceTrackerImpl.getParamURLStrippedOfRandomParam(new URL(ttParams.getParamURL().toString())).toString());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
+34
-53
@@ -132,21 +132,19 @@ public interface DomainFactory {
|
||||
* Provides the capability to obtain the {@link WindTrack}s for the different wind sources. A trivial
|
||||
* implementation is {@link EmptyWindStore} which simply provides new, empty tracks. This is always
|
||||
* available but loses track of the wind, e.g., during server restarts.
|
||||
* @param timeoutInMilliseconds TODO
|
||||
*/
|
||||
TracTracRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry,
|
||||
RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds)
|
||||
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException;
|
||||
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException, IOException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Same as {@link #createRaceTracker(URL, URI, URI, URI, TimePoint, TimePoint, WindStore, TrackedRegattaRegistry)},
|
||||
* only that a predefined {@link Regatta} is used to hold the resulting races.
|
||||
* @param timeoutInMilliseconds TODO
|
||||
*/
|
||||
RaceTracker createRaceTracker(Regatta regatta, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry,
|
||||
RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds)
|
||||
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException,
|
||||
SubscriberInitializationException;
|
||||
SubscriberInitializationException, IOException, InterruptedException;
|
||||
|
||||
BoatClass getOrCreateBoatClass(String competitorClassName);
|
||||
|
||||
@@ -161,7 +159,6 @@ public interface DomainFactory {
|
||||
* must have been created before through
|
||||
* {@link #getOrCreateTrackedRegatta(com.sap.sailing.domain.base.Regatta)} because otherwise the link to
|
||||
* the {@link IEvent} can't be established
|
||||
* @param timeoutInMilliseconds TODO
|
||||
* @param tokenToRetrieveAssociatedRace
|
||||
* used to update the set of{@link RaceDefinition}s received by the {@link RaceCourseReceiver} created by
|
||||
* this call
|
||||
@@ -173,47 +170,39 @@ public interface DomainFactory {
|
||||
IEventSubscriber eventSubscriber, IRaceSubscriber raceSubscriber, boolean useInternalMarkPassingAlgorithm, long timeoutInMilliseconds);
|
||||
|
||||
/**
|
||||
* Creates a {@link RaceDefinition} from a TracTrac {@link IRace} and a
|
||||
* domain {@link Course} definition. The resulting {@link RaceDefinition} is
|
||||
* added to the {@link com.sap.sailing.domain.base.Regatta} to which
|
||||
* <code>trackedRegatta</code> belongs (see
|
||||
* {@link TrackedRegatta#getRegatta()}). It is added to the internal race
|
||||
* cache. The corresponding {@link TrackedRace} object is also created, and
|
||||
* the notification of threads waiting on the race cache such as a blocking
|
||||
* {@link #getAndWaitForRaceDefinition(UUID)} happens only <em>after</em>
|
||||
* the tracked race has been created and the {@link RaceDefinition} was
|
||||
* {@link com.sap.sailing.domain.base.Regatta#addRace(RaceDefinition) added}
|
||||
* to the domain event. This ensures that waiters for the
|
||||
* {@link RaceDefinition} are guaranteed to obtain a valid,
|
||||
* non-<code>null</code> tracked race already immediately after the
|
||||
* notification was sent, and that the {@link RaceDefinition} is already
|
||||
* {@link com.sap.sailing.domain.base.Regatta#getAllRaces() known} by its
|
||||
* containing {@link com.sap.sailing.domain.base.Regatta}.
|
||||
* Creates a {@link RaceDefinition} from a TracTrac {@link IRace} and a domain {@link Course} definition. The
|
||||
* resulting {@link RaceDefinition} is added to the {@link com.sap.sailing.domain.base.Regatta} to which
|
||||
* <code>trackedRegatta</code> belongs (see {@link TrackedRegatta#getRegatta()}). It is added to the internal race
|
||||
* cache. The corresponding {@link TrackedRace} object is also created, and the notification of threads waiting on
|
||||
* the race cache such as a blocking {@link #getAndWaitForRaceDefinition(UUID)} happens only <em>after</em> the
|
||||
* tracked race has been created and the {@link RaceDefinition} was
|
||||
* {@link com.sap.sailing.domain.base.Regatta#addRace(RaceDefinition) added} to the domain event. This ensures that
|
||||
* waiters for the {@link RaceDefinition} are guaranteed to obtain a valid, non-<code>null</code> tracked race
|
||||
* already immediately after the notification was sent, and that the {@link RaceDefinition} is already
|
||||
* {@link com.sap.sailing.domain.base.Regatta#getAllRaces() known} by its containing
|
||||
* {@link com.sap.sailing.domain.base.Regatta}.
|
||||
*
|
||||
* @param raceDefinitionSetToUpdate
|
||||
* if not <code>null</code>, after creating the
|
||||
* {@link TrackedRace}, the {@link RaceDefinition} is
|
||||
* {@link DynamicRaceDefinitionSet#addRaceDefinition(RaceDefinition, DynamicTrackedRace)
|
||||
* added} to that object.
|
||||
* if not <code>null</code>, after creating the {@link TrackedRace}, the {@link RaceDefinition} is
|
||||
* {@link DynamicRaceDefinitionSet#addRaceDefinition(RaceDefinition, DynamicTrackedRace) added} to that
|
||||
* object.
|
||||
* @param runBeforeExposingRace
|
||||
* if not {@code null} then this consumer will be passed the
|
||||
* {@link DynamicTrackedRace} if it was actually created by this
|
||||
* call. This happens while still in the
|
||||
* {@code synchronized(raceCache)} block, therefore before calls
|
||||
* waiting for the race (e.g.,
|
||||
* {@link #getAndWaitForRaceDefinition(UUID)}) return the race.
|
||||
* @param tractracRace TODO
|
||||
*/
|
||||
DynamicTrackedRace getOrCreateRaceDefinitionAndTrackedRace(DynamicTrackedRegatta trackedRegatta, UUID raceId,
|
||||
String raceName, Iterable<com.sap.sailing.domain.base.Competitor> competitors, BoatClass boatClass,
|
||||
Map<Competitor, Boat> competitorBoats, Course course, Iterable<Sideline> sidelines, WindStore windStore,
|
||||
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
|
||||
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, URI courseDesignUpdateURI, UUID tracTracEventUuid,
|
||||
String tracTracUsername, String tracTracPassword, boolean ignoreTracTracMarkPassings, RaceLogResolver raceLogResolver, Consumer<DynamicTrackedRace> runBeforeExposingRace, IRace tractracRace);
|
||||
* if not {@code null} then this consumer will be passed the {@link DynamicTrackedRace} if it was
|
||||
* actually created by this call. This happens while still in the {@code synchronized(raceCache)} block,
|
||||
* therefore before calls waiting for the race (e.g., {@link #getAndWaitForRaceDefinition(UUID)}) return
|
||||
* the race.
|
||||
*/
|
||||
DynamicTrackedRace getOrCreateRaceDefinitionAndTrackedRace(DynamicTrackedRegatta trackedRegatta, UUID raceId,
|
||||
String raceName, Iterable<com.sap.sailing.domain.base.Competitor> competitors, BoatClass boatClass,
|
||||
Map<Competitor, Boat> competitorBoats, Course course, Iterable<Sideline> sidelines, WindStore windStore,
|
||||
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
|
||||
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, URI courseDesignUpdateURI, UUID tracTracEventUuid,
|
||||
String tracTracUsername, String tracTracPassword, boolean ignoreTracTracMarkPassings,
|
||||
RaceLogResolver raceLogResolver, Consumer<DynamicTrackedRace> runBeforeExposingRace, IRace tractracRace);
|
||||
|
||||
/**
|
||||
* The record may be for a single mark or a gate. If for a gate, the
|
||||
* {@link ControlPointPositionData#getIndex() index} is used to determine
|
||||
* which of its marks is affected.
|
||||
* The record may be for a single mark or a gate. If for a gate, the {@link ControlPointPositionData#getIndex()
|
||||
* index} is used to determine which of its marks is affected.
|
||||
*/
|
||||
Mark getMark(TracTracControlPoint controlPoint, int zeroBasedMarkIndex);
|
||||
|
||||
@@ -223,8 +212,6 @@ public interface DomainFactory {
|
||||
|
||||
/**
|
||||
* If the vm argument tractrac.usemarkpassings=false, the RecieverType MARKPASSINGS will not return anything
|
||||
* @param raceLogResolver TODO
|
||||
* @param timeoutInMilliseconds TODO
|
||||
*/
|
||||
Iterable<Receiver> getUpdateReceivers(DynamicTrackedRegatta trackedRegatta, IRace tractracRace, WindStore windStore,
|
||||
long delayToLiveInMillis, Simulator simulator, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, TrackedRegattaRegistry trackedRegattaRegistry,
|
||||
@@ -274,23 +261,17 @@ public interface DomainFactory {
|
||||
* {@code File}) then if this parameter is {@code true} the race will be loaded from the replay file
|
||||
* instead of the {@code storedURI}/{@code liveURI} specified. This is particularly useful for restoring
|
||||
* races if since the last connection the race was migrated to a replay file format.
|
||||
* @param timeoutInMillis TODO
|
||||
*/
|
||||
RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI,
|
||||
URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
|
||||
Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
||||
String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility, boolean trackWind, boolean correctWindDirectionByMagneticDeclination,
|
||||
boolean preferReplayIfAvailable, int timeoutInMillis) throws Exception;
|
||||
|
||||
/**
|
||||
* Removes all knowledge about <code>tractracRace</code> which includes removing it from the race cache, from the
|
||||
* {@link com.sap.sailing.domain.base.Regatta} and, if a {@link TrackedRace} for the corresponding
|
||||
* {@link RaceDefinition} exists, from the {@link TrackedRegatta}. If removing the race from the event, the event is
|
||||
* removed from the event cache such that {@link #getOrCreateDefaultRegatta(IEvent, TrackedRegattaRegistry)} will have to create a new one. Similarly,
|
||||
* if the {@link TrackedRace} that was removed from the {@link TrackedRegatta} was the last one, the
|
||||
* {@link TrackedRegatta} is removed such that {@link #getOrCreateTrackedRegatta(com.sap.sailing.domain.base.Regatta)}
|
||||
* will have to create a new one.
|
||||
* Removes all knowledge about <code>tractracRace</code> from the race cache.
|
||||
*/
|
||||
void removeRace(IEvent tractracEvent, IRace tractracRace, TrackedRegattaRegistry trackedRegattaRegistry);
|
||||
RaceDefinition removeRace(IEvent tractracEvent, IRace tractracRace, Regatta regattaToLoadRaceInto, TrackedRegattaRegistry trackedRegattaRegistry);
|
||||
|
||||
/**
|
||||
* Computes an ID to use for a {@link RaceDefinition} based on the TracTrac race.
|
||||
|
||||
+22
-34
@@ -1,9 +1,7 @@
|
||||
package com.sap.sailing.domain.tractracadapter.impl;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
@@ -457,7 +455,7 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRace(IEvent tractracEvent, IRace tractracRace, TrackedRegattaRegistry trackedRegattaRegistry) {
|
||||
public RaceDefinition removeRace(IEvent tractracEvent, IRace tractracRace, Regatta regattaToLoadRaceInto, TrackedRegattaRegistry trackedRegattaRegistry) {
|
||||
RaceDefinition raceDefinition;
|
||||
synchronized (raceCache) {
|
||||
raceDefinition = getExistingRaceDefinitionForRace(tractracRace.getId());
|
||||
@@ -467,45 +465,33 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
}
|
||||
}
|
||||
if (raceDefinition != null) {
|
||||
// FIXME this only removes the race from its default regatta, not any explicit regatta
|
||||
Pair<String, BoatClass> defaultRegattaNameAndBoatClass = getDefaultRegattaNameAndBoatClass(tractracRace);
|
||||
Pair<String, String> key = new Pair<String, String>(defaultRegattaNameAndBoatClass.getA(),
|
||||
defaultRegattaNameAndBoatClass.getB() == null ? null :
|
||||
defaultRegattaNameAndBoatClass.getB().getName());
|
||||
synchronized (regattaCache) {
|
||||
Regatta regatta = regattaCache.get(key);
|
||||
final Regatta regatta;
|
||||
final Pair<String, String> key;
|
||||
if (regattaToLoadRaceInto != null) {
|
||||
regatta = regattaToLoadRaceInto;
|
||||
key = new Pair<>(regatta.getName(), regatta.getBoatClass().getName());
|
||||
} else {
|
||||
Pair<String, BoatClass> defaultRegattaNameAndBoatClass = getDefaultRegattaNameAndBoatClass(tractracRace);
|
||||
key = new Pair<String, String>(defaultRegattaNameAndBoatClass.getA(),
|
||||
defaultRegattaNameAndBoatClass.getB() == null ? null :
|
||||
defaultRegattaNameAndBoatClass.getB().getName());
|
||||
regatta = regattaCache.get(key);
|
||||
}
|
||||
if (regatta != null) {
|
||||
// The following fixes bug 202: when tracking of multiple races of the same event has been started, this may not
|
||||
// remove any race; however, the event may already have been created by another tracker whose race hasn't
|
||||
// arrived yet and therefore the races list is still empty; therefore, only remove the event if its
|
||||
// race list became empty by the removal performed here.
|
||||
int oldSize = Util.size(regatta.getAllRaces());
|
||||
regatta.removeRace(raceDefinition);
|
||||
if (oldSize > 0 && Util.size(regatta.getAllRaces()) == 0) {
|
||||
if (Util.contains(regatta.getAllRaces(), raceDefinition) && Util.size(regatta.getAllRaces()) == 1) {
|
||||
logger.info("Removing regatta "+regatta.getName()+" ("+regatta.hashCode()+") from TracTrac DomainFactoryImpl");
|
||||
regattaCache.remove(key);
|
||||
weakDefaultRegattaCache.remove(tractracRace);
|
||||
}
|
||||
TrackedRegatta trackedRegatta = trackedRegattaRegistry.getTrackedRegatta(regatta);
|
||||
if (trackedRegatta != null) {
|
||||
// see above; only remove tracked regatta if it *became* empty because of the tracked race removal here
|
||||
final int oldSizeOfTrackedRaces;
|
||||
final int newSizeOfTrackedRaces;
|
||||
trackedRegatta.lockTrackedRacesForWrite();
|
||||
try {
|
||||
oldSizeOfTrackedRaces = Util.size(trackedRegatta.getTrackedRaces());
|
||||
trackedRegatta.removeTrackedRace(raceDefinition);
|
||||
newSizeOfTrackedRaces = Util.size(trackedRegatta.getTrackedRaces());
|
||||
} finally {
|
||||
trackedRegatta.unlockTrackedRacesAfterWrite();
|
||||
}
|
||||
if (oldSizeOfTrackedRaces > 0 && newSizeOfTrackedRaces == 0) {
|
||||
trackedRegattaRegistry.removeTrackedRegatta(regatta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return raceDefinition;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -716,17 +702,19 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TracTracRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry,
|
||||
RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds) throws MalformedURLException,
|
||||
FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException {
|
||||
public TracTracRaceTracker createRaceTracker(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
||||
WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver,
|
||||
RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds)
|
||||
throws URISyntaxException, CreateModelException, SubscriberInitializationException, IOException,
|
||||
InterruptedException {
|
||||
return new TracTracRaceTrackerImpl(this, raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry, raceLogResolver, connectivityParams, timeoutInMilliseconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaceTracker createRaceTracker(Regatta regatta, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds)
|
||||
throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException,
|
||||
SubscriberInitializationException {
|
||||
throws URISyntaxException, CreateModelException,
|
||||
SubscriberInitializationException, IOException, InterruptedException {
|
||||
return new TracTracRaceTrackerImpl(regatta, this, raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry,
|
||||
raceLogResolver, connectivityParams, timeoutInMilliseconds);
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,7 +1,6 @@
|
||||
package com.sap.sailing.domain.tractracadapter.impl;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
@@ -116,9 +115,10 @@ public class RaceTrackingConnectivityParametersImpl extends AbstractRaceTracking
|
||||
|
||||
@Override
|
||||
public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore,
|
||||
RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws MalformedURLException, FileNotFoundException, URISyntaxException,
|
||||
CreateModelException, SubscriberInitializationException {
|
||||
RaceTracker tracker = domainFactory.createRaceTracker(raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry, raceLogResolver, this, timeoutInMilliseconds);
|
||||
RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws URISyntaxException,
|
||||
CreateModelException, SubscriberInitializationException, IOException, InterruptedException {
|
||||
RaceTracker tracker = domainFactory.createRaceTracker(raceLogStore, regattaLogStore, windStore,
|
||||
trackedRegattaRegistry, raceLogResolver, this, timeoutInMilliseconds);
|
||||
return tracker;
|
||||
}
|
||||
|
||||
|
||||
+16
-9
@@ -1,7 +1,6 @@
|
||||
package com.sap.sailing.domain.tractracadapter.impl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -263,7 +262,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
|
||||
TracTracRaceTrackerImpl(DomainFactory domainFactory, RaceLogStore raceLogStore,
|
||||
RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry,
|
||||
RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds)
|
||||
throws URISyntaxException, MalformedURLException, FileNotFoundException, SubscriberInitializationException {
|
||||
throws URISyntaxException, SubscriberInitializationException, IOException, InterruptedException {
|
||||
this(/* regatta */ null, domainFactory, raceLogStore, regattaLogStore, windStore, trackedRegattaRegistry,
|
||||
raceLogResolver, connectivityParams, timeoutInMilliseconds);
|
||||
}
|
||||
@@ -285,7 +284,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
|
||||
TracTracRaceTrackerImpl(final Regatta regatta, DomainFactory domainFactory, RaceLogStore raceLogStore,
|
||||
RegattaLogStore regattaLogStore, WindStore windStore, TrackedRegattaRegistry trackedRegattaRegistry,
|
||||
RaceLogResolver raceLogResolver, RaceTrackingConnectivityParametersImpl connectivityParams, long timeoutInMilliseconds)
|
||||
throws URISyntaxException, MalformedURLException, FileNotFoundException, SubscriberInitializationException {
|
||||
throws URISyntaxException, SubscriberInitializationException, IOException, InterruptedException {
|
||||
super(connectivityParams);
|
||||
final URL paramURL = connectivityParams.getParamURL();
|
||||
final URI liveURI = connectivityParams.getLiveURI();
|
||||
@@ -385,16 +384,19 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
|
||||
} else {
|
||||
effectiveRegatta = regatta;
|
||||
}
|
||||
// removeRace may detach the domain regatta from the domain factory if that
|
||||
// removed the last race; therefore, it's important to getOrCreate the
|
||||
// domain regatta *after* calling removeRace
|
||||
domainFactory.removeRace(tractracRace.getEvent(), tractracRace, trackedRegattaRegistry);
|
||||
// if regatta is still null, no previous assignment of any of the races in this TracTrac event to a Regatta was
|
||||
// found;
|
||||
// in this case, create a default regatta based on the TracTrac event data
|
||||
this.regatta = effectiveRegatta == null ? domainFactory.getOrCreateDefaultRegatta(
|
||||
raceLogStore, regattaLogStore, tractracRace, trackedRegattaRegistry) : effectiveRegatta;
|
||||
trackedRegatta = trackedRegattaRegistry.getOrCreateTrackedRegatta(this.regatta);
|
||||
// removeRace may detach the domain regatta from the domain factory if that
|
||||
// removed the last race; therefore, it's important to getOrCreate the
|
||||
// domain regatta *after* calling removeRace
|
||||
final RaceDefinition raceDefinition = domainFactory.removeRace(tractracRace.getEvent(), tractracRace, this.regatta, trackedRegattaRegistry);
|
||||
if (raceDefinition != null) {
|
||||
trackedRegattaRegistry.removeRace(this.regatta, raceDefinition);
|
||||
}
|
||||
receivers = new HashSet<Receiver>();
|
||||
for (Receiver receiver : domainFactory.getUpdateReceivers(getTrackedRegatta(), delayToLiveInMillis,
|
||||
simulator, windStore, this, trackedRegattaRegistry, raceLogResolver, tractracRace,
|
||||
@@ -460,7 +462,12 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
|
||||
return trackedRegatta;
|
||||
}
|
||||
|
||||
public static Object createID(URL paramURL, URI liveURI, URI storedURI) {
|
||||
public static Object createID(URL paramURL, final URI liveURI, final URI storedURI) {
|
||||
URL paramURLStrippedOfRandomParam = getParamURLStrippedOfRandomParam(paramURL);
|
||||
return new Util.Triple<URL, URI, URI>(paramURLStrippedOfRandomParam, liveURI, storedURI);
|
||||
}
|
||||
|
||||
public static URL getParamURLStrippedOfRandomParam(URL paramURL) {
|
||||
URL paramURLStrippedOfRandomParam;
|
||||
if (paramURL == null) {
|
||||
paramURLStrippedOfRandomParam = null;
|
||||
@@ -496,7 +503,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Util.Triple<URL, URI, URI>(paramURLStrippedOfRandomParam, liveURI, storedURI);
|
||||
return paramURLStrippedOfRandomParam;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.base;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import com.sap.sailing.domain.common.PassingInstruction;
|
||||
import com.sap.sse.common.Renamable;
|
||||
import com.sap.sse.common.Util;
|
||||
|
||||
import difflib.PatchFailedException;
|
||||
@@ -25,7 +26,7 @@ import difflib.PatchFailedException;
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public interface Course extends CourseBase {
|
||||
public interface Course extends CourseBase, Renamable {
|
||||
void lockForRead();
|
||||
|
||||
void unlockAfterRead();
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.sap.sailing.domain.base;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.sap.sailing.domain.base.impl.BoatClassImpl;
|
||||
import com.sap.sailing.domain.base.impl.BoatImpl;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.pairinglist.PairingList;
|
||||
|
||||
public class PairingListLeaderboardAdapter implements PairingList<RaceColumn, Fleet, Competitor, Boat> {
|
||||
|
||||
@Override
|
||||
public Iterable<Pair<Competitor, Boat>> getCompetitors(RaceColumn raceColumn, Fleet fleet) {
|
||||
// TODO add boat to return value (bug4403)
|
||||
List<Pair<Competitor, Boat>> result = new ArrayList<>();
|
||||
int boatIndex = 0;
|
||||
for (Competitor competitor : raceColumn.getCompetitorsRegisteredInRacelog(fleet)) {
|
||||
result.add(new Pair<Competitor, Boat>(competitor,
|
||||
new BoatImpl("Boat " + (boatIndex + 1), new BoatClassImpl("49er", true), "DE" + boatIndex)));
|
||||
boatIndex++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public interface RaceDefinition extends NamedWithID {
|
||||
/**
|
||||
* Gets the boat used by the competitor for this race.
|
||||
*/
|
||||
Boat getBoatOfCompetitorById(Serializable competitorID);
|
||||
Boat getBoatOfCompetitor(Competitor competitor);
|
||||
|
||||
/**
|
||||
* The MD5 hash as produced by
|
||||
|
||||
@@ -24,7 +24,7 @@ import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.common.PassingInstruction;
|
||||
import com.sap.sailing.util.CourseAsWaypointList;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.common.impl.NamedImpl;
|
||||
import com.sap.sse.common.impl.RenamableImpl;
|
||||
import com.sap.sse.concurrent.LockUtil;
|
||||
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
|
||||
|
||||
@@ -32,7 +32,7 @@ import difflib.DiffUtils;
|
||||
import difflib.Patch;
|
||||
import difflib.PatchFailedException;
|
||||
|
||||
public class CourseImpl extends NamedImpl implements Course {
|
||||
public class CourseImpl extends RenamableImpl implements Course {
|
||||
|
||||
private static final long serialVersionUID = -4280487649617132403L;
|
||||
|
||||
|
||||
+4
-4
@@ -27,7 +27,7 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
|
||||
private final Course course;
|
||||
private final LinkedHashMap<Serializable, Competitor> competitorsById;
|
||||
private final Set<Competitor> competitors;
|
||||
private final Map<Serializable, Boat> competitorBoats;
|
||||
private final Map<Competitor, Boat> competitorBoats;
|
||||
private final BoatClass boatClass;
|
||||
private final Serializable id;
|
||||
private final RaceCompetitorIdsAsStringWithMD5Hash raceCompetitorsMD5Hash;
|
||||
@@ -65,7 +65,7 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
|
||||
for (Entry<Competitor, Boat> competitorAndBoat : competitorsAndTheirBoats.entrySet()) {
|
||||
Competitor competitor = competitorsById.get(competitorAndBoat.getKey().getId()); // only assign boat if competitor is part of race
|
||||
if (competitor != null && competitorAndBoat.getValue() != null) {
|
||||
competitorBoats.put(competitor.getId(), competitorAndBoat.getValue());
|
||||
competitorBoats.put(competitor, competitorAndBoat.getValue());
|
||||
} else {
|
||||
logger.warning("Trying to set boat "+competitorAndBoat.getValue()+" for competitor "+competitorAndBoat.getKey()+
|
||||
" which is not part of race "+getName()+"'s set of competitors");
|
||||
@@ -111,7 +111,7 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boat getBoatOfCompetitorById(Serializable competitorID) {
|
||||
return competitorBoats.get(competitorID);
|
||||
public Boat getBoatOfCompetitor(Competitor competitor) {
|
||||
return competitorBoats.get(competitor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,6 +256,11 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFleetsCanRunInParallelToTrue() {
|
||||
RegattaImpl.this.setFleetsCanRunInParallelToTrue();
|
||||
}
|
||||
};
|
||||
this.regattaLikeHelper.addListener(new RegattaLogEventAdditionForwarder(raceColumnListeners));
|
||||
this.raceExecutionOrderCache = new RaceExecutionOrderCache();
|
||||
@@ -856,4 +861,11 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
deregisterer.deregister(deregisterer.analyze());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFleetsCanRunInParallelToTrue() {
|
||||
for (Series series : this.series) {
|
||||
series.setIsFleetsCanRunInParallel(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import com.sap.sailing.domain.base.RaceColumnListener;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Series;
|
||||
import com.sap.sailing.domain.common.impl.RenamableImpl;
|
||||
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
|
||||
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
|
||||
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
|
||||
@@ -24,6 +23,7 @@ import com.sap.sailing.domain.tracking.TrackedRegatta;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
import com.sap.sailing.util.impl.RaceColumnListeners;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.impl.RenamableImpl;
|
||||
|
||||
/**
|
||||
* A series listens on its columns; however, a veto for column addition isn't done here but in a {@link RegattaLeaderboard}.
|
||||
|
||||
+1
-1
@@ -5,10 +5,10 @@ import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Series;
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
import com.sap.sailing.domain.regattalike.IsRegattaLike;
|
||||
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sse.common.Renamable;
|
||||
|
||||
/**
|
||||
* A leaderboard that allows its clients to flexibly modify the race columns arranged in this leaderboard without the
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
package com.sap.sailing.domain.leaderboard;
|
||||
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
import com.sap.sailing.domain.regattalike.IsRegattaLike;
|
||||
import com.sap.sse.common.Renamable;
|
||||
|
||||
public interface FlexibleRaceColumn extends RaceColumn, Renamable {
|
||||
void setIsMedalRace(boolean isMedalRace);
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
package com.sap.sailing.domain.leaderboard;
|
||||
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
|
||||
import com.sap.sse.common.Renamable;
|
||||
|
||||
public interface RegattaLeaderboard extends Renamable, LeaderboardThatHasRegattaLike {
|
||||
Regatta getRegatta();
|
||||
|
||||
+47
-5
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.leaderboard.impl;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@@ -39,6 +40,7 @@ import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
||||
@@ -85,6 +87,9 @@ import com.sap.sse.util.impl.FutureTaskWithTracingGet;
|
||||
public abstract class AbstractLeaderboardWithCache implements Leaderboard {
|
||||
private static final long serialVersionUID = -5651389357061229100L;
|
||||
private static final Logger logger = Logger.getLogger(AbstractLeaderboardWithCache.class.getName());
|
||||
|
||||
private final MaxPointsReason[] MAX_POINTS_REASONS_THAT_IDENTIFY_NON_FINISHED_RACES = new MaxPointsReason[] {
|
||||
MaxPointsReason.DNS, MaxPointsReason.DNF, MaxPointsReason.DNC };
|
||||
private transient LiveLeaderboardUpdater liveLeaderboardUpdater;
|
||||
protected static final ExecutorService executor = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor();
|
||||
private transient LeaderboardDTOCache leaderboardDTOCache;
|
||||
@@ -488,6 +493,11 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
|
||||
+ ". Leaving empty.", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (addOverallDetails) {
|
||||
//this reuses several prior calculated fields, so must be evaluated after them
|
||||
row.totalScoredRaces = this.getTotalRaces(competitor, row, timePoint);
|
||||
}
|
||||
result.rows.put(competitorDTO, row);
|
||||
String displayName = this.getDisplayName(competitor);
|
||||
if (displayName != null) {
|
||||
@@ -499,7 +509,39 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
|
||||
+ (System.currentTimeMillis() - startOfRequestHandling) + "ms");
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private Integer getTotalRaces(Competitor competitor, LeaderboardRowDTO row, TimePoint timePoint) {
|
||||
int amount = 0;
|
||||
for (RaceColumn raceColumn : getRaceColumns()) {
|
||||
// reuse calculations already done earlier
|
||||
LeaderboardEntryDTO entry = row.fieldsByRaceColumnName.get(raceColumn.getName());
|
||||
if (entry.netPoints != null) {
|
||||
if (entry.reasonForMaxPoints.equals(MaxPointsReason.NONE)
|
||||
|| !Util.contains(Arrays.asList(MAX_POINTS_REASONS_THAT_IDENTIFY_NON_FINISHED_RACES),
|
||||
entry.reasonForMaxPoints)) {
|
||||
if (raceColumn instanceof MetaLeaderboardColumn) {
|
||||
Leaderboard leaderBoardForMeta = ((MetaLeaderboardColumn) raceColumn).getLeaderboard();
|
||||
for (RaceColumn subRace : leaderBoardForMeta.getRaceColumns()) {
|
||||
Double netPointsForSubRace = leaderBoardForMeta.getNetPoints(competitor, subRace, timePoint);
|
||||
MaxPointsReason subMaxPointsReason = leaderBoardForMeta.getMaxPointsReason(competitor, subRace, timePoint);
|
||||
if(netPointsForSubRace != null){
|
||||
if (subMaxPointsReason.equals(MaxPointsReason.NONE) || !Util.contains(
|
||||
Arrays.asList(MAX_POINTS_REASONS_THAT_IDENTIFY_NON_FINISHED_RACES),
|
||||
subMaxPointsReason)) {
|
||||
amount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
amount++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param waitForLatestAnalyses
|
||||
* if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
|
||||
@@ -1023,12 +1065,12 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
|
||||
legTypeCache.put(trackedLeg, trackedLegType);
|
||||
}
|
||||
if (legType == trackedLegType) {
|
||||
Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint);
|
||||
if (timeSpentOnDownwind != null) {
|
||||
Duration timeSpentInLegOfType = trackedLegOfCompetitor.getTime(timePoint);
|
||||
if (timeSpentInLegOfType != null) {
|
||||
if (result == null) {
|
||||
result = timeSpentOnDownwind;
|
||||
result = timeSpentInLegOfType;
|
||||
} else {
|
||||
result = result.plus(timeSpentOnDownwind);
|
||||
result = result.plus(timeSpentInLegOfType);
|
||||
}
|
||||
} else {
|
||||
// Although the competitor has started the leg, no value was produced. This
|
||||
|
||||
+10
@@ -104,6 +104,11 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
|
||||
public RaceColumn getRaceColumnByName(String raceColumnName) {
|
||||
return getRaceColumnByName(raceColumnName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFleetsCanRunInParallelToTrue() {
|
||||
// no need to do anything; a FlexibleLeaderboard only has one (default) fleet
|
||||
}
|
||||
};
|
||||
this.regattaLikeHelper.addListener(new RegattaLogEventAdditionForwarder(getRaceColumnListeners()));
|
||||
this.raceExecutionOrderProvider = new RaceExecutionOrderCache();
|
||||
@@ -381,4 +386,9 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFleetsCanRunInParallelToTrue() {
|
||||
// no need to do anything because a FlexibleLeaderboard only has one (default) fleet
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -136,4 +136,8 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R
|
||||
public CompetitorProviderFromRaceColumnsAndRegattaLike getOrCreateCompetitorsProvider() {
|
||||
return getRegatta().getOrCreateCompetitorsProvider();
|
||||
}
|
||||
|
||||
public void setFleetsCanRunInParallelToTrue() {
|
||||
this.getRegattaLike().setFleetsCanRunInParallelToTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,4 +67,6 @@ public interface IsRegattaLike extends Serializable {
|
||||
* @see #getTimeOnTimeFactor(Competitor)
|
||||
*/
|
||||
Duration getTimeOnDistanceAllowancePerNauticalMile(Competitor competitor);
|
||||
|
||||
void setFleetsCanRunInParallelToTrue();
|
||||
}
|
||||
+2
-1
@@ -33,7 +33,8 @@ public interface BaseDoubleVectorFixImporter {
|
||||
* @throws IOException
|
||||
* if there is a problem while reading the file
|
||||
*/
|
||||
void importFixes(InputStream inputStream, Callback callback, String filename, String sourceName, boolean downsample)
|
||||
boolean importFixes(InputStream inputStream, Callback callback, String filename, String sourceName,
|
||||
boolean downsample)
|
||||
throws FormatNotSupportedException, IOException;
|
||||
|
||||
/**
|
||||
|
||||
+4
-1
@@ -12,7 +12,10 @@ import com.sap.sse.common.TimePoint;
|
||||
/**
|
||||
* Definition of importers used by SensorDataImportServlet to do the import of a specific file type.
|
||||
*/
|
||||
public interface DoubleVectorFixImporter extends BaseDoubleVectorFixImporter {
|
||||
public interface DoubleVectorFixImporter extends BaseDoubleVectorFixImporter {
|
||||
|
||||
public static final String EXPEDITION_EXTENDED_TYPE = "EXPEDITION_EXTENDED";
|
||||
|
||||
/**
|
||||
* Creates the {@link RegattaLogEvent} for the DeviceMapping.
|
||||
*/
|
||||
|
||||
+15
-9
@@ -30,7 +30,9 @@ import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
|
||||
* @author Fredrik Teschke
|
||||
*
|
||||
*/
|
||||
public interface GPSFixImporter {
|
||||
public interface GPSFixImporter {
|
||||
public static final String EXPEDITION_TYPE = "Expedition";
|
||||
|
||||
String FILE_EXTENSION_PROPERTY = "fileExt";
|
||||
|
||||
/**
|
||||
@@ -43,16 +45,20 @@ public interface GPSFixImporter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the fixes from the {@code inputStream}, and calls the
|
||||
* {@code callback} with every new fix.
|
||||
* @param inferSpeedAndBearing Should speed and bearing be inferred by looking
|
||||
* at the previous fix, if that data is not directly present within the file?
|
||||
* @throws FormatNotSupportedException If the input format cannot be read. The import process
|
||||
* might then decide to try attempt importing fixes using the next suitable importer.
|
||||
* Retrieves the fixes from the {@code inputStream}, and calls the {@code callback} with every new fix.
|
||||
*
|
||||
* @param sourceName some name that identifies the source, e.g. the file name if a file
|
||||
* @param inferSpeedAndBearing
|
||||
* Should speed and bearing be inferred by looking at the previous fix, if that data is not directly
|
||||
* present within the file?
|
||||
* @throws FormatNotSupportedException
|
||||
* If the input format cannot be read. The import process might then decide to try attempt importing
|
||||
* fixes using the next suitable importer.
|
||||
*
|
||||
* @param sourceName
|
||||
* some name that identifies the source, e.g. the file name if a file
|
||||
* @return returns if import was succesful or not
|
||||
*/
|
||||
void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing,
|
||||
boolean importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing,
|
||||
String sourceName)
|
||||
throws FormatNotSupportedException, IOException;
|
||||
|
||||
|
||||
+15
-6
@@ -24,7 +24,12 @@ public interface RaceTrackingConnectivityParameters {
|
||||
* Starts a {@link RaceTracker} using the connectivity parameters provided by this object. As no specific
|
||||
* {@link Regatta} is provided, this will first look up a regatta for the race from
|
||||
* {@link TrackedRegattaRegistry#getRememberedRegattaForRace(java.io.Serializable)} and if not found will look up or
|
||||
* create a default regatta based on race data such as an event name and the boat class.
|
||||
* create a default regatta based on race data such as an event name and the boat class.<p>
|
||||
*
|
||||
* If a race equal to the one being created by this call already exists in the default regatta, its tracking
|
||||
* will be stopped (if any), the {@link RaceDefinition} will be removed from the default regatta, and the
|
||||
* {@link TrackedRace} will be removed from the {@link TrackedRegatta}. All this happens by means of
|
||||
* calling {@link TrackedRegattaRegistry#removeRace(Regatta, com.sap.sailing.domain.base.RaceDefinition)}.
|
||||
*
|
||||
* @param timeoutInMilliseconds
|
||||
* gives the tracker a possibility to abort tracking the race after so many milliseconds of
|
||||
@@ -37,11 +42,15 @@ public interface RaceTrackingConnectivityParameters {
|
||||
RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws Exception;
|
||||
|
||||
/**
|
||||
* Starts a {@link RaceTracker}, associating the resulting races with the {@link Regatta} passed as argument
|
||||
* instead of using the tracker's domain factory to obtain a default {@link Regatta} object for the tracking
|
||||
* parameters. This is particularly useful if a predefined regatta with {@link Series} and {@link Fleet}s
|
||||
* is to be used.
|
||||
* @param timeoutInMilliseconds TODO
|
||||
* Starts a {@link RaceTracker}, associating the resulting races with the {@link Regatta} passed as argument instead
|
||||
* of using the tracker's domain factory to obtain a default {@link Regatta} object for the tracking parameters.
|
||||
* This is particularly useful if a predefined regatta with {@link Series} and {@link Fleet}s is to be used.
|
||||
* <p>
|
||||
*
|
||||
* If a race equal to the one being created by this call already exists in the {@code regatta}, its tracking will be
|
||||
* stopped (if any), the {@link RaceDefinition} will be removed from the default regatta, and the
|
||||
* {@link TrackedRace} will be removed from the {@link TrackedRegatta}. All this happens by means of calling
|
||||
* {@link TrackedRegattaRegistry#removeRace(Regatta, com.sap.sailing.domain.base.RaceDefinition)}.
|
||||
*/
|
||||
RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore,
|
||||
RaceLogResolver raceLogResolver, long timeoutInMilliseconds) throws Exception;
|
||||
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.domain.tracking;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -26,7 +27,7 @@ public interface RaceTrackingConnectivityParametersHandler {
|
||||
* The types of the value objects must be restricted to thos types that MongoDB can handle as field values,
|
||||
* in particular {@link String}, {@link Number} and {@link UUID}.
|
||||
*/
|
||||
Map<String, Object> mapFrom(RaceTrackingConnectivityParameters params);
|
||||
Map<String, Object> mapFrom(RaceTrackingConnectivityParameters params) throws MalformedURLException;
|
||||
|
||||
/**
|
||||
* Produces a {@link RaceTrackingConnectivityParameters} object from the contents of {@code map}, ignoring the
|
||||
@@ -45,5 +46,5 @@ public interface RaceTrackingConnectivityParametersHandler {
|
||||
* {@link RaceTrackingConnectivityParameters#getTypeIdentifier()} as well as a non-empty set of further
|
||||
* entries that uniquely identify the {@code params} object in the database, in particular for removing.
|
||||
*/
|
||||
Map<String, Object> getKey(RaceTrackingConnectivityParameters params);
|
||||
Map<String, Object> getKey(RaceTrackingConnectivityParameters params) throws MalformedURLException;
|
||||
}
|
||||
|
||||
+23
@@ -4,6 +4,8 @@ import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
|
||||
@@ -28,6 +30,27 @@ public interface TrackedRegattaRegistry {
|
||||
*/
|
||||
DynamicTrackedRegatta getTrackedRegatta(Regatta regatta);
|
||||
|
||||
/**
|
||||
* Removes <code>race</code> and any corresponding {@link #getTrackedRace(Regatta, RaceDefinition) tracked race}
|
||||
* from this service. If it was the last {@link RaceDefinition} in its {@link Regatta} and the regatta
|
||||
* {@link Regatta#isPersistent() is not stored persistently}, the <code>regatta</code> is removed as well and will no
|
||||
* longer be returned by {@link #getAllRegattas()}. The wind tracking is stopped for <code>race</code>.
|
||||
* <p>
|
||||
*
|
||||
* Any {@link RaceTracker} for which <code>race</race> is the last race tracked that is still reachable
|
||||
* from {@link #getAllRegattas()} will be {@link RaceTracker#stop(boolean) stopped}.
|
||||
*
|
||||
* The <code>race</code> will be also removed from all leaderboards containing a column that has <code>race</code>'s
|
||||
* {@link #getTrackedRace(Regatta, RaceDefinition) corresponding} {@link TrackedRace} as its
|
||||
* {@link RaceColumn#getTrackedRace(Fleet)}.
|
||||
*
|
||||
* @param regatta
|
||||
* the regatta from which to remove the race
|
||||
* @param race
|
||||
* the race to remove
|
||||
*/
|
||||
void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,InterruptedException;
|
||||
|
||||
void removeTrackedRegatta(Regatta regatta);
|
||||
|
||||
/**
|
||||
|
||||
+16
-13
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.tracking.Track;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.common.WithID;
|
||||
import com.sap.sse.common.scalablevalue.ScalableValue;
|
||||
|
||||
@@ -39,7 +40,11 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
|
||||
|
||||
private transient TimeRangeCache<Distance> foilingDistanceCache;
|
||||
|
||||
private transient TimeRangeCache<Distance> averageRideHeightCache;
|
||||
/**
|
||||
* Caches the sum of the ride height of all fixes in the interval, paired with the number of fixes
|
||||
* that constituted the basis for the ride height sum.
|
||||
*/
|
||||
private transient TimeRangeCache<Pair<Distance, Long>> averageRideHeightCache;
|
||||
|
||||
/**
|
||||
* If a GPS track was provided at construction time, remember it non-transiently. It is needed when restoring
|
||||
@@ -112,7 +117,7 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
|
||||
}
|
||||
}
|
||||
|
||||
private TimeRangeCache<Distance> createAverageRideHeightCache(ItemType trackedItem) {
|
||||
private TimeRangeCache<Pair<Distance, Long>> createAverageRideHeightCache(ItemType trackedItem) {
|
||||
return createTimeRangeCache(trackedItem, "averageRideHeightCache");
|
||||
}
|
||||
|
||||
@@ -177,13 +182,15 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
|
||||
|
||||
@Override
|
||||
public Distance getAverageRideHeight(TimePoint from, TimePoint to) {
|
||||
return getValueSum(from, to, /* nullElement */ Distance.NULL, Distance::add, averageRideHeightCache,
|
||||
/* valueCalculator */ new Track.TimeRangeValueCalculator<Distance>() {
|
||||
final Pair<Distance, Long> nullElement = new Pair<>(Distance.NULL, 0l);
|
||||
Pair<Distance, Long> rideHeightSumAndCount = getValueSum(from, to, nullElement,
|
||||
(a, b)->new Pair<>(a.getA().add(b.getA()), a.getB()+b.getB()),
|
||||
averageRideHeightCache,
|
||||
/* valueCalculator */ new Track.TimeRangeValueCalculator<Pair<Distance, Long>>() {
|
||||
@Override
|
||||
public Distance calculate(TimePoint from, TimePoint to) {
|
||||
Distance result;
|
||||
public Pair<Distance, Long> calculate(TimePoint from, TimePoint to) {
|
||||
Distance sum = Distance.NULL;
|
||||
int count = 0;
|
||||
long count = 0;
|
||||
for (final BravoFix fix : getFixes(from, true, to, true)) {
|
||||
final Distance rideHeight = fix.getRideHeight();
|
||||
if (rideHeight != null) {
|
||||
@@ -191,14 +198,10 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
result = sum.scale(1./count);
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
return new Pair<>(sum, count);
|
||||
}
|
||||
});
|
||||
return rideHeightSumAndCount.getB() == 0l ? null : rideHeightSumAndCount.getA().scale(1./(double) rideHeightSumAndCount.getB());
|
||||
}
|
||||
|
||||
private boolean isFoiling(BravoFix fix) {
|
||||
|
||||
+1
-1
@@ -114,7 +114,7 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener {
|
||||
}
|
||||
|
||||
public boolean add(CrossTrackErrorSumAndNumberOfFixes entry) {
|
||||
return getInternalRawFixes().add(entry);
|
||||
return super.add(entry, /* replace */ true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -294,7 +294,9 @@ public class MaxSpeedCache<ItemType, FixType extends GPSFix> implements GPSTrack
|
||||
} else {
|
||||
speedAtFixTime = track.getEstimatedSpeed(fix.getTimePoint());
|
||||
}
|
||||
if (speedAtFixTime != null && speedAtFixTime.compareTo(max) > 0) {
|
||||
// accept max speeds only if they don't exceed our validity threshold
|
||||
if (speedAtFixTime != null && speedAtFixTime.compareTo(GPSFixTrack.DEFAULT_MAX_SPEED_FOR_SMOOTHING) < 0
|
||||
&& speedAtFixTime.compareTo(max) > 0) {
|
||||
max = speedAtFixTime;
|
||||
maxSpeedFix = fix;
|
||||
}
|
||||
|
||||
@@ -154,4 +154,10 @@
|
||||
version="0.5.0.qualifier"
|
||||
unpack="false"/>
|
||||
|
||||
<plugin
|
||||
id="org.mp4parser.isoparser"
|
||||
download-size="0"
|
||||
install-size="0"
|
||||
version="1.9.31"/>
|
||||
|
||||
</feature>
|
||||
|
||||
@@ -28,11 +28,14 @@
|
||||
<link rel="stylesheet" type="text/css" href="RaceBoard.css">
|
||||
|
||||
|
||||
<link href="js/video-js/video-js.css" rel="stylesheet">
|
||||
<link href="js/video-js/video-js.min.css" rel="stylesheet">
|
||||
<link href="js/video-js/video-js-sapsailing.css" rel="stylesheet">
|
||||
<script src="js/video-js/video.js"></script>
|
||||
<script src="js/vjs.youtube.js"></script>
|
||||
<script src="js/vjs.vimeo.js"></script>
|
||||
<script src="js/video-js/video.min.js"></script>
|
||||
<script src="js/three.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="js/videojs-panorama.min.css"/>
|
||||
<script src="js/videojs-panorama.v5.min.js"></script>
|
||||
<script src="js/Youtube.min.js"></script>
|
||||
<script src="js/Vimeo.js"></script>
|
||||
<script>
|
||||
videojs.options.techOrder = ['vimeo', 'youtube', 'html5'];
|
||||
</script>
|
||||
|
||||
@@ -14,12 +14,16 @@
|
||||
href="/sailing-fontface-1.0.cache.css">
|
||||
<script src="js/jquery-1.11.1.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="js/jquery.slick/slick-1.5.8.css"/>
|
||||
<script type="text/javascript" src="js/jquery.slick/slick-1.5.8.min.js"></script>
|
||||
<link href="js/video-js/video-js.css" rel="stylesheet">
|
||||
<link href="js/video-js/video-js-sapsailing.css" rel="stylesheet">
|
||||
<script src="js/video-js/video.js"></script>
|
||||
<script src="js/vjs.youtube.js"></script>
|
||||
<script src="js/vjs.vimeo.js"></script>
|
||||
<script type="text/javascript" src="js/jquery.slick/slick-1.5.8.min.js"></script>
|
||||
|
||||
<link href="js/video-js/video-js.min.css" rel="stylesheet">
|
||||
<link href="js/video-js/video-js-sapsailing.css" rel="stylesheet">
|
||||
<script src="js/video-js/video.min.js"></script>
|
||||
<script src="js/three.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="js/videojs-panorama.min.css"/>
|
||||
<script src="js/videojs-panorama.v5.min.js"></script>
|
||||
<script src="js/Youtube.min.js"></script>
|
||||
<script src="js/Vimeo.js"></script>
|
||||
<script>
|
||||
videojs.options.techOrder = ['vimeo', 'youtube', 'html5'];
|
||||
</script>
|
||||
|
||||
@@ -55,7 +55,8 @@ Require-Bundle: com.sap.sailing.domain,
|
||||
com.sap.sse.shared.android,
|
||||
com.sap.sailing.competitorimport,
|
||||
com.sap.sailing.expeditionconnector,
|
||||
com.sap.sailing.expeditionconnector.common
|
||||
com.sap.sailing.expeditionconnector.common,
|
||||
org.mp4parser.isoparser;bundle-version="1.9.31"
|
||||
Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Import-Package: javax.servlet;version="3.1.0",
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/** Add css rules here for your application. */
|
||||
.clear {
|
||||
clear: both;
|
||||
font-size: 1px;
|
||||
height: 0px;
|
||||
line-height: 0px;
|
||||
margin: -1px 0 0;
|
||||
width: 100%; }
|
||||
|
||||
.cover {
|
||||
left: -1000px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: -1000px; }
|
||||
|
||||
html {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #fff;
|
||||
font-family: 'Open Sans', Arial, Verdana, sans-serif;
|
||||
line-height: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/** Example rules used by the template application (remove for your app) */
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: #777777;
|
||||
margin: 40px 0px 70px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate; }
|
||||
|
||||
|
||||
.refreshAndSettings {
|
||||
background: #f2f2f2;
|
||||
border-bottom: 2px solid #fff;
|
||||
border-top: 2px solid #fff;
|
||||
height: 30px; }
|
||||
|
||||
.refreshAndSettings table td {
|
||||
padding: 0 10px 0 0;}
|
||||
|
||||
.refreshAndSettings table table td{
|
||||
padding: 0 0 0 0;}
|
||||
|
||||
input.magnifier{
|
||||
display: block;
|
||||
margin: 3px auto 0 auto !important;
|
||||
}
|
||||
|
||||
body, table td, select, button {
|
||||
font-family: 'Open Sans', Arial, Verdana, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.errorLabel {
|
||||
color: #FF0000;
|
||||
}
|
||||
|
||||
.abstractChartPanel-importantMessageOfChart {
|
||||
font-family: 'Open Sans',Arial,Verdana,sans-serif;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
height: 70px;
|
||||
margin: 0 auto;
|
||||
padding: 49px 0 0 80px;
|
||||
width: 300px;
|
||||
color: #7c7c7c;
|
||||
background: url(images/important-message.png) left center no-repeat;
|
||||
}
|
||||
|
||||
.LeaderboardHeader {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-family: 'Open Sans', Arial, Verdana, sans-serif;
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.leaderboardHeading {
|
||||
font-size: 16px;
|
||||
font-family: 'Open Sans', Arial, Verdana, sans-serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.gwt-DialogBox {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-family: 'Open Sans Condensed', sans-serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.trackingQuality-circle {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
-moz-border-radius-topleft: 50%;
|
||||
-webkit-border-top-left-radius: 50%;
|
||||
-o-border-top-left-radius: 50%;
|
||||
-ms-border-top-left-radius: 50%;
|
||||
border-top-left-radius: 50%;
|
||||
-moz-border-radius-topright: 50%;
|
||||
-webkit-border-top-right-radius: 50%;
|
||||
-o-border-top-right-radius: 50%;
|
||||
-ms-border-top-right-radius: 50%;
|
||||
border-top-right-radius: 50%;
|
||||
-moz-border-radius-bottomleft: 50%;
|
||||
-webkit-border-bottom-left-radius: 50%;
|
||||
-o-border-bottom-left-radius: 50%;
|
||||
-ms-border-bottom-left-radius: 50%;
|
||||
border-bottom-left-radius: 50%;
|
||||
-moz-border-radius-bottomright: 50%;
|
||||
-webkit-border-bottom-right-radius: 50%;
|
||||
-o-border-bottom-right-radius: 50%;
|
||||
-ms-border-bottom-right-radius: 50%;
|
||||
border-bottom-right-radius: 50%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.circleYellow {
|
||||
background: #fbba00;
|
||||
}
|
||||
|
||||
.circleGreen {
|
||||
background: #8ab54e;
|
||||
}
|
||||
|
||||
.circleRed {
|
||||
background: #f01010;
|
||||
}
|
||||
|
||||
.busyIndicator-Simple {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.busyIndicator-Circle {
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<!--[if lt IE 9]>
|
||||
<meta http-equiv="REFRESH" content="0; url=http://www.sapsailing.com/browser-info.html">
|
||||
<![endif]-->
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<title>SAP Sailing Analytics PairingList</title>
|
||||
<meta content='width = device-width, initial-scale = 1.0, user-scalable = yes' name='viewport'>
|
||||
<meta content='yes' name='apple-mobile-web-app-capable'>
|
||||
<meta content='black' name='apple-mobile-web-app-status-bar-style'>
|
||||
<!-- <link href='iphone-splash-screen.png' rel='apple-touch-startup-image'> -->
|
||||
<link href='images/sap-sailing-app-icon.png' rel='apple-touch-icon'>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="images/sap.ico" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/sailing-normalize-3.0.2.cache.css">
|
||||
<link rel="stylesheet" type="text/css" href="/sailing-fontface-1.0.cache.css">
|
||||
<link rel="stylesheet" type="text/css" href="PairingList.css">
|
||||
|
||||
<script type="text/javascript" src="js/jquery-1.5.2.min.js"></script>
|
||||
|
||||
<script type="text/javascript" src="com.sap.sailing.gwt.ui.PairingList/com.sap.sailing.gwt.ui.PairingList.nocache.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
|
||||
<!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
|
||||
<noscript>
|
||||
<div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
|
||||
Your web browser must have JavaScript enabled
|
||||
in order for this application to display correctly.
|
||||
</div>
|
||||
</noscript>
|
||||
</body>
|
||||
</html>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
|
||||
* {
|
||||
font-family: 'Open Sans', sans-serif !important;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,19 @@
|
||||
<link rel="stylesheet" type="text/css" href="RaceBoard.css">
|
||||
|
||||
<script type="text/javascript" src="js/jquery-1.5.2.min.js"></script>
|
||||
|
||||
|
||||
<link href="js/video-js/video-js.min.css" rel="stylesheet">
|
||||
<link href="js/video-js/video-js-sapsailing.css" rel="stylesheet">
|
||||
<script src="js/video-js/video.min.js"></script>
|
||||
<script src="js/three.js"></script>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="js/videojs-panorama.min.css" />
|
||||
<script src="js/videojs-panorama.v5.min.js"></script>
|
||||
<script src="js/Vimeo.js"></script>
|
||||
<script>
|
||||
videojs.options.techOrder = [ 'vimeo', 'html5' ];
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" language="javascript" src="com.sap.sailing.gwt.ui.RaceBoard/com.sap.sailing.gwt.ui.RaceBoard.nocache.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-style PRETTY -incremental -workDir "${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work" -war "${project_loc:com.sap.sailing.gwt.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/YoutubePopup.html com.sap.sailing.gwt.ui.YoutubePopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.autoplay.AutoPlay"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-style PRETTY -incremental -workDir "${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work" -war "${project_loc:com.sap.sailing.gwt.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.PairingList -startupUrl /gwt/PairingList.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/YoutubePopup.html com.sap.sailing.gwt.ui.YoutubePopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.autoplay.AutoPlay"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms1024m -Xmx3072m -Dgwt.watchFileChanges=false"/>
|
||||
</launchConfiguration>
|
||||
|
||||
@@ -9,6 +9,19 @@
|
||||
<link rel="stylesheet" type="text/css" href="/sailing-normalize-3.0.2.cache.css">
|
||||
<link rel="stylesheet" type="text/css" href="VideoPopup.css">
|
||||
|
||||
<link href="js/video-js/video-js.min.css" rel="stylesheet">
|
||||
<link href="js/video-js/video-js-sapsailing.css" rel="stylesheet">
|
||||
<script src="js/video-js/video.min.js"></script>
|
||||
<script src="js/three.js"></script>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="js/videojs-panorama.min.css" />
|
||||
<script src="js/videojs-panorama.v5.min.js"></script>
|
||||
<script src="js/Youtube.min.js"></script>
|
||||
<script src="js/Vimeo.js"></script>
|
||||
<script>
|
||||
videojs.options.techOrder = [ 'vimeo', 'youtube', 'html5' ];
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
html {overflow: auto;}
|
||||
html, body, div, iframe {margin: 0px; padding: 0px; height: 100%; border: none;}
|
||||
|
||||
@@ -51,5 +51,8 @@ bin.includes = META-INF/,\
|
||||
com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart/,\
|
||||
Calendar.html,\
|
||||
imprint.json,\
|
||||
licenses/
|
||||
licenses/,\
|
||||
PairingList.css,\
|
||||
PairingList.html,\
|
||||
com.sap.sailing.gwt.ui.PairingList/
|
||||
output.. = WEB-INF/classes/
|
||||
|
||||
@@ -697,7 +697,7 @@
|
||||
},
|
||||
{
|
||||
"name": "video-js",
|
||||
"version": "4.12.10",
|
||||
"version": "5.20.3",
|
||||
"owner": "Brightcove, Inc.",
|
||||
"homepage": "https://github.com/videojs/video.js",
|
||||
"acknowledgements": [
|
||||
@@ -707,7 +707,7 @@
|
||||
},
|
||||
{
|
||||
"name": "video-js-vimeo",
|
||||
"version": "master-20140814",
|
||||
"version": "2.0.0",
|
||||
"owner": "Benoit Tremblay",
|
||||
"homepage": "http://github.com/eXon/videojs-vimeo/",
|
||||
"acknowledgements": [
|
||||
@@ -717,13 +717,33 @@
|
||||
},
|
||||
{
|
||||
"name": "video-js-youtube",
|
||||
"version": "1.2.5",
|
||||
"version": "2.0.8",
|
||||
"owner": "Benoit Tremblay",
|
||||
"homepage": "http://github.com/eXon/videojs-youtube/",
|
||||
"acknowledgements": [
|
||||
"Copyright (c) 2015 Benoit Tremblay <trembl.ben@gmail.com>"
|
||||
],
|
||||
"key": "MITL"
|
||||
},
|
||||
{
|
||||
"name": "three.js",
|
||||
"version": "76",
|
||||
"owner": "\"Mr.doob\"",
|
||||
"homepage": "https://threejs.org/",
|
||||
"acknowledgements": [
|
||||
"Copyright © 2010-2017 three.js authors"
|
||||
],
|
||||
"key": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "video-js-panorma",
|
||||
"version": "0.1.5",
|
||||
"owner": "Wensheng Yan",
|
||||
"homepage": "https://github.com/yanwsh/videojs-panorama/",
|
||||
"acknowledgements": [
|
||||
"Copyright 2016 yanwsh@gmail.com"
|
||||
],
|
||||
"key": "Apache License 2.0"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
|
||||
@@ -0,0 +1,657 @@
|
||||
/* The MIT License (MIT)
|
||||
Copyright (c) 2014-2015 Benoit Tremblay <trembl.ben@gmail.com>
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE. */
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var VimeoState = {
|
||||
UNSTARTED: -1,
|
||||
ENDED: 0,
|
||||
PLAYING: 1,
|
||||
PAUSED: 2,
|
||||
BUFFERING: 3
|
||||
};
|
||||
|
||||
var Tech = videojs.getComponent('Tech');
|
||||
|
||||
var Vimeo = videojs.extend(Tech, {
|
||||
constructor: function(options, ready) {
|
||||
Tech.call(this, options, ready);
|
||||
if(options.poster != "") {this.setPoster(options.poster);}
|
||||
this.setSrc(this.options_.source.src, true);
|
||||
|
||||
// Set the vjs-vimeo class to the player
|
||||
// Parent is not set yet so we have to wait a tick
|
||||
setTimeout(function() {
|
||||
this.el_.parentNode.className += ' vjs-vimeo';
|
||||
}.bind(this));
|
||||
|
||||
},
|
||||
|
||||
dispose: function() {
|
||||
this.el_.parentNode.className = this.el_.parentNode.className.replace(' vjs-vimeo', '');
|
||||
},
|
||||
|
||||
createEl: function() {
|
||||
this.vimeo = {};
|
||||
this.vimeoInfo = {};
|
||||
this.baseUrl = 'https://player.vimeo.com/video/';
|
||||
this.baseApiUrl = 'http://www.vimeo.com/api/v2/video/';
|
||||
this.videoId = Vimeo.parseUrl(this.options_.source.src).videoId;
|
||||
|
||||
this.iframe = document.createElement('iframe');
|
||||
this.iframe.setAttribute('id', this.options_.techId);
|
||||
this.iframe.setAttribute('title', 'Vimeo Video Player');
|
||||
this.iframe.setAttribute('class', 'vimeoplayer');
|
||||
this.iframe.setAttribute('src', this.baseUrl + this.videoId + '?api=1&player_id=' + this.options_.techId);
|
||||
this.iframe.setAttribute('frameborder', '0');
|
||||
this.iframe.setAttribute('scrolling', 'no');
|
||||
this.iframe.setAttribute('marginWidth', '0');
|
||||
this.iframe.setAttribute('marginHeight', '0');
|
||||
this.iframe.setAttribute('webkitAllowFullScreen', '0');
|
||||
this.iframe.setAttribute('mozallowfullscreen', '0');
|
||||
this.iframe.setAttribute('allowFullScreen', '0');
|
||||
|
||||
var divWrapper = document.createElement('div');
|
||||
divWrapper.setAttribute('style', 'margin:0 auto;padding-bottom:56.25%;width:100%;height:0;position:relative;overflow:hidden;');
|
||||
divWrapper.setAttribute('class', 'vimeoFrame');
|
||||
divWrapper.appendChild(this.iframe);
|
||||
|
||||
if (!_isOnMobile && !this.options_.ytControls) {
|
||||
var divBlocker = document.createElement('div');
|
||||
divBlocker.setAttribute('class', 'vjs-iframe-blocker');
|
||||
divBlocker.setAttribute('style', 'position:absolute;top:0;left:0;width:100%;height:100%');
|
||||
|
||||
// In case the blocker is still there and we want to pause
|
||||
divBlocker.onclick = function() {
|
||||
this.onPause();
|
||||
}.bind(this);
|
||||
|
||||
divWrapper.appendChild(divBlocker);
|
||||
}
|
||||
|
||||
if (Vimeo.isApiReady) {
|
||||
this.initPlayer();
|
||||
} else {
|
||||
Vimeo.apiReadyQueue.push(this);
|
||||
}
|
||||
|
||||
if(this.options_.poster == "") {
|
||||
$.getJSON(this.baseApiUrl + this.videoId + '.json?callback=?', {format: "json"}, (function(_this){
|
||||
return function(data) {
|
||||
// Set the low resolution first
|
||||
_this.setPoster(data[0].thumbnail_large);
|
||||
};
|
||||
})(this));
|
||||
}
|
||||
|
||||
return divWrapper;
|
||||
},
|
||||
|
||||
initPlayer: function() {
|
||||
var self = this;
|
||||
var vimeoVideoID = Vimeo.parseUrl(this.options_.source.src).videoId;
|
||||
//load vimeo
|
||||
if (this.vimeo && this.vimeo.api) {
|
||||
this.vimeo.api('unload');
|
||||
delete this.vimeo;
|
||||
}
|
||||
|
||||
self.vimeo = $f(self.iframe);
|
||||
|
||||
self.vimeoInfo = {
|
||||
state: VimeoState.UNSTARTED,
|
||||
volume: 1,
|
||||
muted: false,
|
||||
muteVolume: 1,
|
||||
time: 0,
|
||||
duration: 0,
|
||||
buffered: 0,
|
||||
url: self.baseUrl + self.videoId,
|
||||
error: null
|
||||
};
|
||||
|
||||
this.vimeo.addEvent('ready', function(id){
|
||||
self.onReady();
|
||||
|
||||
self.vimeo.addEvent('loadProgress', function(data, id){ self.onLoadProgress(data); });
|
||||
self.vimeo.addEvent('playProgress', function(data, id){ self.onPlayProgress(data); });
|
||||
self.vimeo.addEvent('play', function(id){ self.onPlay(); });
|
||||
self.vimeo.addEvent('pause', function(id){ self.onPause(); });
|
||||
self.vimeo.addEvent('finish', function(id){ self.onFinish(); });
|
||||
self.vimeo.addEvent('seek', function(data, id){ self.onSeek(data); });
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
onReady: function(){
|
||||
this.isReady_ = true;
|
||||
this.triggerReady();
|
||||
this.trigger('loadedmetadata');
|
||||
if (this.startMuted) {
|
||||
this.setMuted(true);
|
||||
this.startMuted = false;
|
||||
}
|
||||
},
|
||||
|
||||
onLoadProgress: function(data) {
|
||||
var durationUpdate = !this.vimeoInfo.duration;
|
||||
this.vimeoInfo.duration = data.duration;
|
||||
this.vimeoInfo.buffered = data.percent;
|
||||
this.trigger('progress');
|
||||
if (durationUpdate) this.trigger('durationchange');
|
||||
},
|
||||
onPlayProgress: function(data) {
|
||||
this.vimeoInfo.time = data.seconds;
|
||||
this.trigger('timeupdate');
|
||||
},
|
||||
onPlay: function() {
|
||||
this.vimeoInfo.state = VimeoState.PLAYING;
|
||||
this.trigger('play');
|
||||
},
|
||||
onPause: function() {
|
||||
this.vimeoInfo.state = VimeoState.PAUSED;
|
||||
this.trigger('pause');
|
||||
},
|
||||
onFinish: function() {
|
||||
this.vimeoInfo.state = VimeoState.ENDED;
|
||||
this.trigger('ended');
|
||||
},
|
||||
onSeek: function(data) {
|
||||
this.trigger('seeking');
|
||||
this.vimeoInfo.time = data.seconds;
|
||||
this.trigger('timeupdate');
|
||||
this.trigger('seeked');
|
||||
},
|
||||
onError: function(error){
|
||||
this.error = error;
|
||||
this.trigger('error');
|
||||
},
|
||||
|
||||
error: function() {
|
||||
switch (this.errorNumber) {
|
||||
case 2:
|
||||
return { code: 'Unable to find the video' };
|
||||
|
||||
case 5:
|
||||
return { code: 'Error while trying to play the video' };
|
||||
|
||||
case 100:
|
||||
return { code: 'Unable to find the video' };
|
||||
|
||||
case 101:
|
||||
case 150:
|
||||
return { code: 'Playback on other Websites has been disabled by the video owner.' };
|
||||
}
|
||||
|
||||
return { code: 'Vimeo unknown error (' + this.errorNumber + ')' };
|
||||
},
|
||||
|
||||
src: function() {
|
||||
return this.source;
|
||||
},
|
||||
|
||||
poster: function() {
|
||||
return this.poster_;
|
||||
},
|
||||
|
||||
setPoster: function(poster) {
|
||||
this.poster_ = poster;
|
||||
},
|
||||
|
||||
setSrc: function(source) {
|
||||
if (!source || !source.src) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.source = source;
|
||||
this.url = Vimeo.parseUrl(source.src);
|
||||
|
||||
if (!this.options_.poster) {
|
||||
if (this.url.videoId) {
|
||||
$.getJSON(this.baseApiUrl + this.videoId + '.json?callback=?', {format: "json"}, (function(_this){
|
||||
return function(data) {
|
||||
// Set the low resolution first
|
||||
_this.poster_ = data[0].thumbnail_small;
|
||||
};
|
||||
})(this));
|
||||
|
||||
// Check if their is a high res
|
||||
this.checkHighResPoster();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options_.autoplay && !_isOnMobile) {
|
||||
if (this.isReady_) {
|
||||
this.play();
|
||||
} else {
|
||||
this.playOnReady = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
supportsFullScreen: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
//TRIGGER
|
||||
load : function(){},
|
||||
play : function(){ this.vimeo.api('play'); },
|
||||
pause : function(){ this.vimeo.api('pause'); },
|
||||
paused : function(){
|
||||
return this.vimeoInfo.state !== VimeoState.PLAYING &&
|
||||
this.vimeoInfo.state !== VimeoState.BUFFERING;
|
||||
},
|
||||
|
||||
currentTime : function(){ return this.vimeoInfo.time || 0; },
|
||||
|
||||
setCurrentTime :function(seconds){
|
||||
this.vimeo.api('seekTo', seconds);
|
||||
this.player_.trigger('timeupdate');
|
||||
},
|
||||
|
||||
duration :function(){ return this.vimeoInfo.duration || 0; },
|
||||
buffered :function(){ return videojs.createTimeRange(0, (this.vimeoInfo.buffered*this.vimeoInfo.duration) || 0); },
|
||||
|
||||
volume :function() { return (this.vimeoInfo.muted)? this.vimeoInfo.muteVolume : this.vimeoInfo.volume; },
|
||||
setVolume :function(percentAsDecimal){
|
||||
this.vimeo.api('setvolume', percentAsDecimal);
|
||||
this.vimeoInfo.volume = percentAsDecimal;
|
||||
this.player_.trigger('volumechange');
|
||||
},
|
||||
currentSrc :function() {
|
||||
return this.el_.src;
|
||||
},
|
||||
muted :function() { return this.vimeoInfo.muted || false; },
|
||||
setMuted :function(muted) {
|
||||
if (muted) {
|
||||
this.vimeoInfo.muteVolume = this.vimeoInfo.volume;
|
||||
this.setVolume(0);
|
||||
} else {
|
||||
this.setVolume(this.vimeoInfo.muteVolume);
|
||||
}
|
||||
|
||||
this.vimeoInfo.muted = muted;
|
||||
this.player_.trigger('volumechange');
|
||||
},
|
||||
|
||||
// Tries to get the highest resolution thumbnail available for the video
|
||||
checkHighResPoster: function(){
|
||||
var uri = '';
|
||||
|
||||
try {
|
||||
|
||||
$.getJSON(this.baseApiUrl + this.videoId + '.json?callback=?', {format: "json"}, (function(_uri){
|
||||
return function(data) {
|
||||
// Set the low resolution first
|
||||
_uri = data[0].thumbnail_large;
|
||||
};
|
||||
})(uri));
|
||||
|
||||
var image = new Image();
|
||||
image.onload = function(){
|
||||
// Onload thumbnail
|
||||
if('naturalHeight' in this){
|
||||
if(this.naturalHeight <= 90 || this.naturalWidth <= 120) {
|
||||
this.onerror();
|
||||
return;
|
||||
}
|
||||
} else if(this.height <= 90 || this.width <= 120) {
|
||||
this.onerror();
|
||||
return;
|
||||
}
|
||||
|
||||
this.poster_ = uri;
|
||||
this.trigger('posterchange');
|
||||
}.bind(this);
|
||||
image.onerror = function(){};
|
||||
image.src = uri;
|
||||
}
|
||||
catch(e){}
|
||||
}
|
||||
});
|
||||
|
||||
Vimeo.isSupported = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
Vimeo.canPlaySource = function(e) {
|
||||
return (e.type === 'video/vimeo');
|
||||
};
|
||||
|
||||
var _isOnMobile = /(iPad|iPhone|iPod|Android)/g.test(navigator.userAgent);
|
||||
|
||||
Vimeo.parseUrl = function(url) {
|
||||
var result = {
|
||||
videoId: null
|
||||
};
|
||||
|
||||
var regex = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/;
|
||||
var match = url.match(regex);
|
||||
|
||||
if (match) {
|
||||
result.videoId = match[5];
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
function injectCss() {
|
||||
var css = // iframe blocker to catch mouse events
|
||||
'.vjs-vimeo .vjs-iframe-blocker { display: none; }' +
|
||||
'.vjs-vimeo.vjs-user-inactive .vjs-iframe-blocker { display: block; }' +
|
||||
'.vjs-vimeo .vjs-poster { background-size: cover; }' +
|
||||
'.vjs-vimeo { height:100%; }' +
|
||||
'.vimeoplayer { width:100%; height:180%; position:absolute; left:0; top:-40%; }';
|
||||
|
||||
var head = document.head || document.getElementsByTagName('head')[0];
|
||||
|
||||
var style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
|
||||
if (style.styleSheet){
|
||||
style.styleSheet.cssText = css;
|
||||
} else {
|
||||
style.appendChild(document.createTextNode(css));
|
||||
}
|
||||
|
||||
head.appendChild(style);
|
||||
}
|
||||
|
||||
Vimeo.apiReadyQueue = [];
|
||||
|
||||
var vimeoIframeAPIReady = function() {
|
||||
Vimeo.isApiReady = true;
|
||||
injectCss();
|
||||
|
||||
for (var i = 0; i < Vimeo.apiReadyQueue.length; ++i) {
|
||||
Vimeo.apiReadyQueue[i].initPlayer();
|
||||
}
|
||||
};
|
||||
|
||||
vimeoIframeAPIReady();
|
||||
|
||||
videojs.registerTech('Vimeo', Vimeo);
|
||||
|
||||
|
||||
|
||||
// Froogaloop API -------------------------------------------------------------
|
||||
|
||||
// From https://github.com/vimeo/player-api/blob/master/javascript/froogaloop.js
|
||||
// Init style shamelessly stolen from jQuery http://jquery.com
|
||||
var Froogaloop = (function(){
|
||||
// Define a local copy of Froogaloop
|
||||
function Froogaloop(iframe) {
|
||||
// The Froogaloop object is actually just the init constructor
|
||||
return new Froogaloop.fn.init(iframe);
|
||||
}
|
||||
|
||||
var eventCallbacks = {},
|
||||
hasWindowEvent = false,
|
||||
isReady = false,
|
||||
slice = Array.prototype.slice,
|
||||
playerOrigin = '*';
|
||||
|
||||
Froogaloop.fn = Froogaloop.prototype = {
|
||||
element: null,
|
||||
|
||||
init: function(iframe) {
|
||||
if (typeof iframe === "string") {
|
||||
iframe = document.getElementById(iframe);
|
||||
}
|
||||
|
||||
this.element = iframe;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/*
|
||||
* Calls a function to act upon the player.
|
||||
*
|
||||
* @param {string} method The name of the Javascript API method to call. Eg: 'play'.
|
||||
* @param {Array|Function} valueOrCallback params Array of parameters to pass when calling an API method
|
||||
* or callback function when the method returns a value.
|
||||
*/
|
||||
api: function(method, valueOrCallback) {
|
||||
if (!this.element || !method) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var self = this,
|
||||
element = self.element,
|
||||
target_id = element.id !== '' ? element.id : null,
|
||||
params = !isFunction(valueOrCallback) ? valueOrCallback : null,
|
||||
callback = isFunction(valueOrCallback) ? valueOrCallback : null;
|
||||
|
||||
// Store the callback for get functions
|
||||
if (callback) {
|
||||
storeCallback(method, callback, target_id);
|
||||
}
|
||||
|
||||
postMessage(method, params, element);
|
||||
return self;
|
||||
},
|
||||
|
||||
/*
|
||||
* Registers an event listener and a callback function that gets called when the event fires.
|
||||
*
|
||||
* @param eventName (String): Name of the event to listen for.
|
||||
* @param callback (Function): Function that should be called when the event fires.
|
||||
*/
|
||||
addEvent: function(eventName, callback) {
|
||||
if (!this.element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var self = this,
|
||||
element = self.element,
|
||||
target_id = element.id !== '' ? element.id : null;
|
||||
|
||||
|
||||
storeCallback(eventName, callback, target_id);
|
||||
|
||||
// The ready event is not registered via postMessage. It fires regardless.
|
||||
if (eventName != 'ready') {
|
||||
postMessage('addEventListener', eventName, element);
|
||||
}
|
||||
else if (eventName == 'ready' && isReady) {
|
||||
callback.call(null, target_id);
|
||||
}
|
||||
|
||||
return self;
|
||||
},
|
||||
|
||||
/*
|
||||
* Unregisters an event listener that gets called when the event fires.
|
||||
*
|
||||
* @param eventName (String): Name of the event to stop listening for.
|
||||
*/
|
||||
removeEvent: function(eventName) {
|
||||
if (!this.element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var self = this,
|
||||
element = self.element,
|
||||
target_id = element.id !== '' ? element.id : null,
|
||||
removed = removeCallback(eventName, target_id);
|
||||
|
||||
// The ready event is not registered
|
||||
if (eventName != 'ready' && removed) {
|
||||
postMessage('removeEventListener', eventName, element);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles posting a message to the parent window.
|
||||
*
|
||||
* @param method (String): name of the method to call inside the player. For api calls
|
||||
* this is the name of the api method (api_play or api_pause) while for events this method
|
||||
* is api_addEventListener.
|
||||
* @param params (Object or Array): List of parameters to submit to the method. Can be either
|
||||
* a single param or an array list of parameters.
|
||||
* @param target (HTMLElement): Target iframe to post the message to.
|
||||
*/
|
||||
function postMessage(method, params, target) {
|
||||
if (!target.contentWindow.postMessage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var data = JSON.stringify({
|
||||
method: method,
|
||||
value: params
|
||||
});
|
||||
|
||||
target.contentWindow.postMessage(data, playerOrigin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event that fires whenever the window receives a message from its parent
|
||||
* via window.postMessage.
|
||||
*/
|
||||
function onMessageReceived(event) {
|
||||
var data, method;
|
||||
|
||||
try {
|
||||
data = JSON.parse(event.data);
|
||||
method = data.event || data.method;
|
||||
}
|
||||
catch(e) {
|
||||
//fail silently... like a ninja!
|
||||
}
|
||||
|
||||
if (method == 'ready' && !isReady) {
|
||||
isReady = true;
|
||||
}
|
||||
|
||||
// Handles messages from the vimeo player only
|
||||
if (!(/^https?:\/\/player.vimeo.com/).test(event.origin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (playerOrigin === '*') {
|
||||
playerOrigin = event.origin;
|
||||
}
|
||||
|
||||
var value = data.value,
|
||||
eventData = data.data,
|
||||
target_id = target_id === '' ? null : data.player_id,
|
||||
|
||||
callback = getCallback(method, target_id),
|
||||
params = [];
|
||||
|
||||
if (!callback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
params.push(value);
|
||||
}
|
||||
|
||||
if (eventData) {
|
||||
params.push(eventData);
|
||||
}
|
||||
|
||||
if (target_id) {
|
||||
params.push(target_id);
|
||||
}
|
||||
|
||||
return params.length > 0 ? callback.apply(null, params) : callback.call();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stores submitted callbacks for each iframe being tracked and each
|
||||
* event for that iframe.
|
||||
*
|
||||
* @param eventName (String): Name of the event. Eg. api_onPlay
|
||||
* @param callback (Function): Function that should get executed when the
|
||||
* event is fired.
|
||||
* @param target_id (String) [Optional]: If handling more than one iframe then
|
||||
* it stores the different callbacks for different iframes based on the iframe's
|
||||
* id.
|
||||
*/
|
||||
function storeCallback(eventName, callback, target_id) {
|
||||
if (target_id) {
|
||||
if (!eventCallbacks[target_id]) {
|
||||
eventCallbacks[target_id] = {};
|
||||
}
|
||||
eventCallbacks[target_id][eventName] = callback;
|
||||
}
|
||||
else {
|
||||
eventCallbacks[eventName] = callback;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves stored callbacks.
|
||||
*/
|
||||
function getCallback(eventName, target_id) {
|
||||
if (target_id && eventCallbacks[target_id]) {
|
||||
return eventCallbacks[target_id][eventName];
|
||||
}
|
||||
else if (eventCallbacks[eventName]) {
|
||||
return eventCallbacks[eventName];
|
||||
}
|
||||
}
|
||||
|
||||
function removeCallback(eventName, target_id) {
|
||||
if (target_id && eventCallbacks[target_id]) {
|
||||
if (!eventCallbacks[target_id][eventName]) {
|
||||
return false;
|
||||
}
|
||||
eventCallbacks[target_id][eventName] = null;
|
||||
}
|
||||
else {
|
||||
if (!eventCallbacks[eventName]) {
|
||||
return false;
|
||||
}
|
||||
eventCallbacks[eventName] = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isFunction(obj) {
|
||||
return !!(obj && obj.constructor && obj.call && obj.apply);
|
||||
}
|
||||
|
||||
function isArray(obj) {
|
||||
return toString.call(obj) === '[object Array]';
|
||||
}
|
||||
|
||||
// Give the init function the Froogaloop prototype for later instantiation
|
||||
Froogaloop.fn.init.prototype = Froogaloop.fn;
|
||||
|
||||
// Listens for the message event.
|
||||
// W3C
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener('message', onMessageReceived, false);
|
||||
}
|
||||
// IE
|
||||
else {
|
||||
window.attachEvent('onmessage', onMessageReceived);
|
||||
}
|
||||
|
||||
// Expose froogaloop to the global object
|
||||
return (window.Froogaloop = window.$f = Froogaloop);
|
||||
|
||||
})();
|
||||
})();
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user