Merge remote-tracking branch 'origin/master' into bug2610_lifecycle

# Conflicts:
#	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/PlayerActivity.java
#	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java
#	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
#	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
#	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
#	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties
#	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties
#	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java
#	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java
#	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java
#	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardViewConfiguration.java
This commit is contained in:
fmittag
2016-05-13 13:51:23 +02:00
2128 changed files with 236766 additions and 28809 deletions
+1
View File
@@ -33,3 +33,4 @@ out/
local.properties
git.xml
mobile/github-advrecyclerview_lib/bin
captures
+75
View File
@@ -0,0 +1,75 @@
## Server Landscape
### Interconnect AWS regions
For being able to serve low latency connection to the american continent we run several replica server in front of an ELB in AWS region US-East (N.Virginia).
Our Master and eventual further replicas needed in EU region are deployed in AWS region EU-West (Ireland) for low latency connections towards RabbitMQ and MongoDB.
Two interconnect the two AWS regions and subnets, I established a site2site VPN tunnel between us-east-1c and eu-west-1c region. The global VPC routes are used to route the traffic for the private subnets (172.16.0.0 in EU and 172.30.0.0 in US-East) towards the according VPN gateway instance, which then tunnels it to the other region. The result is that both regions can communicate internally with each other, so we don't need to tunnel via reverse SSH tunnels or even more worse, open ports (like MongoDB) to the public.
See [here](http://aws.amazon.com/articles/0639686206802544) for the basic tunnel setup, which is established.
Simply make sure here, to use the according availibility zones to the instances between the regions can interact with each other. All required internal subnets are configured to the "SAP Sailing analytics" and the "DB/Messaging" Security Group in both regions.
The VPN setup can be easily expanded to further regions like APJ.
### Load balancers and Route53
This setup requires two ELB's; one in each region. Put instances behind the according ELB in your region as usual.
The only difference here is, that Route53 entries differ a little bit from the usual standard. There are event subdomain entries (crw2016) for different geo-based locations set, which will handle and forward requests to the "best" load balancer which is set up in each region. See [here](https://wiki.sapsailing.com/wiki/amazon-ec2#amazon-ec2-for-sap-sailing-analytics_howto_using-latency-based-dns-across-regions) for the basic setup of latency-based or geo-based Route53 entries.
### Technical event meta information
```
MONGODB_NAME=crw2016
SERVER_NAME=CRW2016
REPLICATION_CHANNEL=crw2016
EVENT=56f62045-de57-4c5e-be6c-a2339d9c9ece
```
### Instances used
**EU-West Ireland**
- ELB Load Balancer
- External DNS: `CRW2016-EU-554516968.eu-west-1.elb.amazonaws.com`
- Master 1 (Live)
- Internal DNS: `ip-172-31-21-180.eu-west-1.compute.internal`
- External DNS: `ec2-52-50-179-44.eu-west-1.compute.amazonaws.com`
**US-East N. Virginia**
- ELB Load Balancer
- External DNS: `CRW2016-US-EAST-1076258956.us-east-1.elb.amazonaws.com`
- Replica 1
- Internal DNS: `ip-172-30-2-176.ec2.internal`
- External DNS: `ec2-52-90-229-141.compute-1.amazonaws.com`
### Starting up further masters
```
MONGODB_NAME=crw2016
REPLICATION_CHANNEL=crw2016
SERVER_NAME=CRW2016
USE_ENVIRONMENT=live-master-server
MEMORY=25600m
INSTALL_FROM_RELEASE=build-201604090936
SERVER_STARTUP_NOTIFY=steffen.tobias.wagner@sap.com
EVENT=56f62045-de57-4c5e-be6c-a2339d9c9ece
```
### Starting up further replicas
```
INSTALL_FROM_RELEASE=build-201604090936
USE_ENVIRONMENT=live-replica-server
REPLICATE_MASTER_SERVLET_HOST=172.31.21.180
REPLICATE_MASTER_EXCHANGE_NAME=crw2016
REPLICATE_ON_START=com.sap.sailing.server.impl.RacingEventServiceImpl,com.sap.sse.security.impl.SecurityServiceImpl,com.sap.sse.filestorage.impl.FileStorageManagementServiceImpl,com.sap.sse.mail.impl.MailServiceImpl,com.sap.sailing.polars.impl.PolarDataServiceImpl
SERVER_NAME=CRW2016
MONGODB_NAME=crw2016-replica
EVENT_ID=56f62045-de57-4c5e-be6c-a2339d9c9ece
SERVER_STARTUP_NOTIFY=steffen.tobias.wagner@sap.com
```
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -6,7 +6,8 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0-alpha1'
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.0'
}
}
+120
View File
@@ -0,0 +1,120 @@
#!/bin/sh
ANDROID_RELEASE_BRANCH=android-xmake-release
RELEASE_BRANCH=rel-1.4
APP_MANIFESTS="mobile/com.sap.sailing.android.tracking.app/AndroidManifest.xml mobile/com.sap.sailing.buoy.positioning/AndroidManifest.xml mobile/com.sap.sailing.racecommittee.app/AndroidManifest.xml"
GIT_REMOTE=origin
UPDATE_ANDROID_MANIFEST_VERSIONS=1
UPDATE_POM_VERSIONS=1
PERFORM_GIT_OPERATIONS=1
increment_version_code() {
MANIFEST=$1
echo "Incrementing version code and version name for $MANIFEST"
OLD_VERSION_CODE=`grep 'android:versionCode="[0-9]*"' $MANIFEST | sed -e 's/^.*android:versionCode="\([0-9]*\)".*$/\1/'`
NEW_VERSION_CODE=$(($OLD_VERSION_CODE + 1))
echo $MANIFEST: OLD_VERSION_CODE is $OLD_VERSION_CODE, NEW_VERSION_CODE is $NEW_VERSION_CODE
OLD_MINOR_VERSION=`grep 'android:versionName="[0-9]*\.[0-9]*"' $MANIFEST | sed -e 's/^.*android:versionName="\([0-9]*\)\.\([0-9]*\)".*$/\2/'`
NEW_MINOR_VERSION=$((OLD_MINOR_VERSION + 1))
echo $MANIFEST: OLD_MINOR_VERSION is $OLD_MINOR_VERSION, NEW_MINOR_VERSION is $NEW_MINOR_VERSION
sed --in-place -e "s/android:versionCode=\"$OLD_VERSION_CODE\"/android:versionCode=\"$NEW_VERSION_CODE\"/" -e "s/android:versionName=\"\([0-9]*\)\.$OLD_MINOR_VERSION\"/android:versionName=\"\1.$NEW_MINOR_VERSION\"/" "$MANIFEST"
}
upgrade_pom_and_manifest_versions() {
POM=$1
NEW_POM_VERSION=$2
POM_WITH_PARENT_SPEC_REMOVED="${POM}.parentspecremoved"
PARENT_SPEC="${POM}.parentspec"
RESTORED_POM_WITH_NEW_VERSION_AND_PARENT_SPEC="${POM}.restored"
POM_BACKUP="${POM}.bak"
cp "$POM" "$POM_BACKUP"
# find the area to remove
snip_area_start_line=`grep -n SNIP_MARKER_START pom.xml | sed -e 's/^\([0-9]*\):.*$/\1/'`
snip_area_end_line=`grep -n SNIP_MARKER_END pom.xml | sed -e 's/^\([0-9]*\):.*$/\1/'`
sed -e "$snip_area_start_line,$snip_area_end_line d" <"$POM" >"$POM_WITH_PARENT_SPEC_REMOVED"
sed -e "1,$((snip_area_start_line - 1)) d" -e "$((snip_area_end_line + 1)),$ d" <"$POM" >"$PARENT_SPEC"
cp "$POM_WITH_PARENT_SPEC_REMOVED" "$POM"
echo mvn -Dtycho.mode=maven org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=$NEW_POM_VERSION
mvn -Dtycho.mode=maven org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=$NEW_POM_VERSION
sed --in-place -e 's/$/\\/' "$PARENT_SPEC"
cat "$POM" | sed -e "$snip_area_start_line i \\`cat "$PARENT_SPEC"`" | sed -e "$snip_area_start_line,$snip_area_end_line s/\\\\$//" >"$RESTORED_POM_WITH_NEW_VERSION_AND_PARENT_SPEC"
cp "$RESTORED_POM_WITH_NEW_VERSION_AND_PARENT_SPEC" "$POM"
}
if [ $# -eq 0 ]; then
echo "buildAndUpdateProduct [-m -p -g -r <git-remote>] CR-Id: <TheJCWBCRId>"
echo ""
echo "Request a new Java Correction Workbench (JCWB) Correction Request ID at:"
echo " https://css.wdf.sap.corp/sap(bD1lbiZjPTAwMQ==)/bc/bsp/spn/jcwb/default.htm?newCMForProject=sapsailingcapture#"
echo "See more usage details in the sapsailing.com wiki at:"
echo " https://wiki.sapsailing.com/wiki/building-and-deploying#building-deploying-stopping-and-starting-server-instances_app-build-process-for-ios-and-android_xmake-build-for-android-apps"
echo "-m Disable upgrading the AndroidManifest.xml versionCode and versionName"
echo "-p Disable upgrading the pom.xml and MANIFEST.MF versions"
echo "-g Disable the final git push operation to refs/for/$RELEASE_BRANCH"
echo "-r The git remote; defaults to origin"
echo ""
echo "Example: $0 CR-Id: 012003146900000486712016"
exit 2
fi
options='mpgr:'
while getopts $options option
do
case $option in
m) UPDATE_ANDROID_MANIFEST_VERSIONS=0;;
p) UPDATE_POM_VERSIONS=0;;
g) PERFORM_GIT_OPERATIONS=0;;
r) GIT_REMOTE=$OPTARG;;
\?) echo "Invalid option"
exit 4;;
esac
done
shift $((OPTIND-1))
# change directory to the git root, assuming this script is in the configuration/ subfolder
GIT_DIR="`dirname \"$0\"`/.."
echo GIT_DIR: $GIT_DIR
JCWB_CHANGE_REQUEST_ID_GIT_TAG=$*
echo "Releasing based on Java Correction Workbench (JCWB) Correction Request ID tag $JCWB_CHANGE_REQUEST_ID_GIT_TAG"
cd "$GIT_DIR"
git fetch $GIT_REMOTE
git checkout $ANDROID_RELEASE_BRANCH
git merge -m "Merging $GIT_REMOTE/$ANDROID_RELEASE_BRANCH" $GIT_REMOTE/$ANDROID_RELEASE_BRANCH
git fetch $GIT_REMOTE $RELEASE_BRANCH:$RELEASE_BRANCH
git merge -m "Merging $RELEASE_BRANCH into $ANDROID_RELEASE_BRANCH, probably incorporating version setting to -SNAPSHOT" $GIT_REMOTE/$RELEASE_BRANCH
git push $GIT_REMOTE $ANDROID_RELEASE_BRANCH:$ANDROID_RELEASE_BRANCH
# Patch the AndroidManifest.xml files to upgrade the android:versionCode sequential counter relevant for the PlayStore
# and the android:versionName which is what the user sees and which we expect to follow a major.minor version scheme,
# where this script increments the minor version by one
if [ "$UPDATE_ANDROID_MANIFEST_VERSIONS" = "1" ]; then
for m in $APP_MANIFESTS; do
increment_version_code $m
done
fi
# Determine the version in the root pom.xml; if a -SNAPSHOT version, simply remove the -SNAPSHOT;
# otherwise, increment micro version by one and use the Tycho version plugin to update;
# For this to work, the <parent> specification needs to be removed from the top-level pom which later
# needs to be re-activated before committing everything
OLD_POM_VERSION=`head -n 15 pom.xml | grep '<version>.*</version>' | sed -e 's/^.*<version>\(.*\)<\/version>.*$/\1/'`
echo $OLD_POM_VERSION | grep -q -- "-SNAPSHOT"
if [ "$?" = "0" ]; then
# found a -SNAPSHOT identifier; assuming this was for the next release already; simply remove -SNAPSHOT
NEW_POM_VERSION=`echo $OLD_POM_VERSION | sed -e 's/-SNAPSHOT//'`
else
# no -SNAPSHOT found; increment the micro version by one
NEW_POM_VERSION=`echo $OLD_POM_VERSION | (IFS=. read major minor micro; echo ${major}.${minor}.$(($micro + 1)) )`
fi
echo OLD_POM_VERSION is $OLD_POM_VERSION, NEW_POM_VERSION is $NEW_POM_VERSION
if [ "$UPDATE_POM_VERSIONS" = "1" ]; then
upgrade_pom_and_manifest_versions pom.xml $NEW_POM_VERSION
fi
# Now commit the version changes and amend the commit using the change request ID tag:
git commit -a -m "Upgraded Android apps from version $OLD_POM_VERSION to $NEW_POM_VERSION"
git commit --amend -m "`git show -s --pretty=format:%s%n%n%b`
$JCWB_CHANGE_REQUEST_ID_GIT_TAG"
if [ "$PERFORM_GIT_OPERATIONS" = "1" ]; then
git push $GIT_REMOTE $ANDROID_RELEASE_BRANCH:refs/for/$RELEASE_BRANCH
fi
+2
View File
@@ -28,6 +28,7 @@ SAP is at the center of today’s technology revolution, developing innovations
* [[Amazon EC2|wiki/amazon-ec2]]
* [[SSL / HTTPS Support|wiki/ssl-support]]
* [[EC2 Backup Strategy|wiki/amazon-ec2-backup-strategy]]
* [[EC2 mail relaying|wiki/mail-relaying]]
* [[TracTrac|wiki/tractrac-lifecycle]]
* [[Analytics on a stick|wiki/analytics-on-a-stick]]
* [[Operating Igtimi WindBots/windbot-operations]]
@@ -72,6 +73,7 @@ SAP is at the center of today’s technology revolution, developing innovations
* [[Travem&uuml;nder Woche 2014 event page|wiki/tw2014]]
* [[505 worlds Kiel 2014 event page|wiki/505-worlds-kiel-2014]]
* [[Kieler Woche event page|wiki/kieler-woche-2015]]
* [[Charleston Race Week 2016|wiki/Charleston-Race-Week-2016]]
* Lean DI Support
* [[Setting up internal Jenkins on SAP Monsoon|wiki/setting-up-jenkins-on-sap-monsoon]]
@@ -283,7 +283,6 @@
7F44095B19F65B5B0021BF22 /* CoreData.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 7F44095919F65B5B0021BF22 /* CoreData.xcdatamodeld */; };
7F4D2F541A1C8EE50038E7D1 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7F4D2F531A1C8EE50038E7D1 /* Images.xcassets */; };
7F4EEDBA1A6D2BAC0026F711 /* CheckIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F4EEDB91A6D2BAC0026F711 /* CheckIn.swift */; };
7F72C73A1A2DC51900F48EE8 /* SplashScreenDummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F72C7391A2DC51900F48EE8 /* SplashScreenDummy.swift */; };
7F83F15619F154F000C53328 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F83F15519F154F000C53328 /* AppDelegate.swift */; };
7F83F15819F154F000C53328 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F83F15719F154F000C53328 /* HomeViewController.swift */; };
7F83F15B19F154F000C53328 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7F83F15919F154F000C53328 /* Main.storyboard */; };
@@ -317,6 +316,7 @@
7FED45CA1A25F5910022505F /* event1.json in Resources */ = {isa = PBXBuildFile; fileRef = 7FED45C41A25F5900022505F /* event1.json */; };
7FED45CC1A25F5910022505F /* leaderboard1.json in Resources */ = {isa = PBXBuildFile; fileRef = 7FED45C61A25F5900022505F /* leaderboard1.json */; };
9E95C3D61C723AB30016632B /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E95C3D51C723AB30016632B /* Constants.swift */; };
9ECBDDAE1C9C4F0300F76AEB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9ECBDDAD1C9C4F0300F76AEB /* LaunchScreen.storyboard */; };
C8603D4A1C528832002B2F28 /* Acknowledgements.markdown in Sources */ = {isa = PBXBuildFile; fileRef = C8603D491C528832002B2F28 /* Acknowledgements.markdown */; };
C8603D511C52892A002B2F28 /* Acknowledgements.markdown in Resources */ = {isa = PBXBuildFile; fileRef = C8603D491C528832002B2F28 /* Acknowledgements.markdown */; };
C8A4400B1C18153600DF70A0 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = C8A4400A1C18153600DF70A0 /* Settings.bundle */; };
@@ -647,7 +647,6 @@
7F44095A19F65B5B0021BF22 /* CoreData.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CoreData.xcdatamodel; sourceTree = "<group>"; };
7F4D2F531A1C8EE50038E7D1 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
7F4EEDB91A6D2BAC0026F711 /* CheckIn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckIn.swift; sourceTree = "<group>"; };
7F72C7391A2DC51900F48EE8 /* SplashScreenDummy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashScreenDummy.swift; sourceTree = "<group>"; };
7F83F15019F154F000C53328 /* SAPTracker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SAPTracker.app; sourceTree = BUILT_PRODUCTS_DIR; };
7F83F15419F154F000C53328 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7F83F15519F154F000C53328 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -686,6 +685,7 @@
7FED45C41A25F5900022505F /* event1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = event1.json; sourceTree = "<group>"; };
7FED45C61A25F5900022505F /* leaderboard1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = leaderboard1.json; sourceTree = "<group>"; };
9E95C3D51C723AB30016632B /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
9ECBDDAD1C9C4F0300F76AEB /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
C8603D491C528832002B2F28 /* Acknowledgements.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Acknowledgements.markdown; sourceTree = "<group>"; };
C8A4400A1C18153600DF70A0 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
C8B31F771B68DAD400C8A809 /* Pods.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Pods.xcodeproj; path = Pods/Pods.xcodeproj; sourceTree = "<group>"; };
@@ -1059,6 +1059,7 @@
7F72C73F1A2DEA2100F48EE8 /* Resources */ = {
isa = PBXGroup;
children = (
9ECBDDAD1C9C4F0300F76AEB /* LaunchScreen.storyboard */,
7F83F15919F154F000C53328 /* Main.storyboard */,
7F4D2F531A1C8EE50038E7D1 /* Images.xcassets */,
7F87CA9D1A2CCE8B0076C617 /* OpenSans-Bold.ttf */,
@@ -1077,7 +1078,6 @@
7FD9D3021A1236080058FEAD /* Extensions.swift */,
7F87CAA51A2CD6BA0076C617 /* Appearance.h */,
7F87CAA61A2CD6BA0076C617 /* Appearance.m */,
7F72C7391A2DC51900F48EE8 /* SplashScreenDummy.swift */,
9E95C3D51C723AB30016632B /* Constants.swift */,
);
name = Helpers;
@@ -1219,7 +1219,6 @@
TargetAttributes = {
7F83F14F19F154F000C53328 = {
CreatedOnToolsVersion = 6.0.1;
DevelopmentTeam = RRE8EFVCE3;
};
7F83F16419F154F000C53328 = {
CreatedOnToolsVersion = 6.0.1;
@@ -1517,6 +1516,7 @@
7F14A1501A13B9A6001C1ECB /* CW.png in Resources */,
7F14A1141A13B9A6001C1ECB /* _northern-cyprus.png in Resources */,
7F14A13E1A13B9A6001C1ECB /* BW.png in Resources */,
9ECBDDAE1C9C4F0300F76AEB /* LaunchScreen.storyboard in Resources */,
7F14A1481A13B9A6001C1ECB /* CK.png in Resources */,
7F14A11B1A13B9A6001C1ECB /* _unknown.png in Resources */,
7F14A19B1A13B9A6001C1ECB /* LI.png in Resources */,
@@ -1617,7 +1617,6 @@
7F06590419F8094C0077FF95 /* DeviceUDIDManager.swift in Sources */,
7F3AA7C81A1CD7D700F8861F /* SendGPSFixController.swift in Sources */,
7FCDB6641A1636610075D7D9 /* LeaderBoard.swift in Sources */,
7F72C73A1A2DC51900F48EE8 /* SplashScreenDummy.swift in Sources */,
C8603D4A1C528832002B2F28 /* Acknowledgements.markdown in Sources */,
7F8B8D411A373B0200C34B88 /* SpeedViewController.swift in Sources */,
9E95C3D61C723AB30016632B /* Constants.swift in Sources */,
@@ -1789,9 +1788,9 @@
baseConfigurationReference = EA431711E588F5A2B31A62AC /* Pods.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
INFOPLIST_FILE = SAPTracker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1801,7 +1800,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
SWIFT_OBJC_BRIDGING_HEADER = SAPTracker/bridge.h;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -1810,9 +1809,9 @@
baseConfigurationReference = CF98F25F363113C51C7CB1F3 /* Pods.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
INFOPLIST_FILE = SAPTracker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -1822,7 +1821,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
SWIFT_OBJC_BRIDGING_HEADER = SAPTracker/bridge.h;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@@ -63,7 +63,7 @@ public class APIManager: NSObject {
// set up reachability
let operationQueue = manager!.operationQueue
manager!.reachabilityManager.setReachabilityStatusChangeBlock({(AFNetworkReachabilityStatus status) -> Void in
manager!.reachabilityManager.setReachabilityStatusChangeBlock({(status) -> Void in
switch (status) {
case AFNetworkReachabilityStatus.ReachableViaWWAN, AFNetworkReachabilityStatus.ReachableViaWiFi:
operationQueue?.suspended = false
@@ -106,7 +106,7 @@ public class APIManager: NSObject {
}
public func teamImage(competitorId: String!, result: (imageUrl: String?) -> Void) {
getTeam(competitorId, success: { (AFHTTPRequestOperation operation, AnyObject teamResponseObject) -> Void in
getTeam(competitorId, success: { (operation, teamResponseObject) -> Void in
if let team = teamResponseObject as? [String: AnyObject],
let imageUrl = team["imageUri"] as? String {
@@ -115,7 +115,7 @@ public class APIManager: NSObject {
result(imageUrl: nil)
}
}, failure: { (AFHTTPRequestOperation operation, NSError error) -> Void in
}, failure: { (operation, error) -> Void in
// No image & that's ok
result(imageUrl: nil)
})
@@ -12,6 +12,7 @@ class AboutViewController: UIViewController {
@IBOutlet weak var versionDescriptionLabel: UILabel!
@IBOutlet weak var versionLabel: UILabel!
@IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad();
@@ -21,7 +22,16 @@ class AboutViewController: UIViewController {
} else {
versionLabel.text = "-"
}
// add logo to top left
let imageView = UIImageView(image: UIImage(named: "sap_logo"))
let barButtonItem = UIBarButtonItem(customView: imageView)
navigationItem.leftBarButtonItem = barButtonItem
}
override func viewDidLayoutSubviews() {
textView.scrollRectToVisible(CGRect(x: 0, y: 0, width: 1, height: 1), animated: false)
}
@IBAction func done(sender: AnyObject) {
presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
@@ -44,6 +44,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
let rootViewController = self.window!.rootViewController as! UINavigationController
rootViewController.popToRootViewControllerAnimated(false)
rootViewController.dismissViewControllerAnimated(false, completion: nil)
@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="pqQ-jd-OwW">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9532" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="pqQ-jd-OwW">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
</dependencies>
<customFonts key="customFonts">
@@ -47,6 +48,7 @@
<string>OpenSans</string>
<string>OpenSans</string>
<string>OpenSans</string>
<string>OpenSans</string>
</mutableArray>
<mutableArray key="OpenSans-Semibold.ttf">
<string>OpenSans-Semibold</string>
@@ -208,20 +210,41 @@
<outlet property="btnScan" destination="Ieu-ch-jeI" id="FWs-OF-b4i"/>
<outlet property="tableView" destination="OXU-ft-TXZ" id="1BA-dy-00A"/>
<outlet property="tableViewHeight" destination="yuo-wx-0A1" id="pFK-q1-1DI"/>
<outlet property="vSplashScreen" destination="4Od-pk-xCd" id="3sj-Mx-34I"/>
<segue destination="G3a-SV-Maf" kind="show" identifier="Regatta" id="FMW-37-go1"/>
<segue destination="plm-Uw-c62" kind="show" identifier="Scan" id="U8l-If-CrF"/>
<segue destination="4Fb-v3-mkB" kind="presentation" identifier="Settings" id="Xxn-jp-xEB"/>
<segue destination="dfN-Ch-vpm" kind="presentation" identifier="About" id="QMa-LQ-D8c"/>
<segue destination="4Fb-v3-mkB" kind="presentation" identifier="SettingsFromHome" id="Xxn-jp-xEB"/>
<segue destination="dfN-Ch-vpm" kind="presentation" identifier="AboutFromHome" id="QMa-LQ-D8c"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="x5A-6p-PRh" sceneMemberID="firstResponder"/>
<view contentMode="scaleToFill" id="4Od-pk-xCd">
<rect key="frame" x="0.0" y="0.0" width="600" height="536"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="applogo" translatesAutoresizingMaskIntoConstraints="NO" id="K04-Lb-fSm">
<rect key="frame" x="117.5" y="220" width="365" height="96"/>
<constraints>
<constraint firstAttribute="width" secondItem="K04-Lb-fSm" secondAttribute="height" multiplier="365:96" id="iTN-7Q-YkX"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="K04-Lb-fSm" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="4Od-pk-xCd" secondAttribute="leading" constant="30" id="0ML-xS-Lk4"/>
<constraint firstItem="K04-Lb-fSm" firstAttribute="width" secondItem="K04-Lb-fSm" secondAttribute="height" multiplier="365:96" id="YeC-DN-aQX"/>
<constraint firstItem="K04-Lb-fSm" firstAttribute="centerY" secondItem="4Od-pk-xCd" secondAttribute="centerY" id="lJN-eB-asJ"/>
<constraint firstItem="K04-Lb-fSm" firstAttribute="centerX" secondItem="4Od-pk-xCd" secondAttribute="centerX" id="lP3-oq-Tj2"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="K04-Lb-fSm" secondAttribute="trailing" constant="30" id="tNv-UR-6vj"/>
</constraints>
</view>
</objects>
<point key="canvasLocation" x="1222" y="-307"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="e3W-CJ-soQ">
<objects>
<navigationController id="pqQ-jd-OwW" sceneMemberID="viewController">
<navigationController storyboardIdentifier="rootViewController" id="pqQ-jd-OwW" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" translucent="NO" id="x3H-m5-yJB">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
@@ -299,7 +322,7 @@
</constraint>
</constraints>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CDA-ep-bjo">
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CDA-ep-bjo">
<rect key="frame" x="200" y="48" width="200" height="33"/>
<color key="backgroundColor" white="1" alpha="0.5" colorSpace="calibratedWhite"/>
<constraints>
@@ -345,13 +368,13 @@
<rect key="frame" x="0.0" y="114" width="600" height="40"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="GER 17" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="iIf-9z-7fe">
<rect key="frame" x="527.5" y="8" width="56.5" height="24"/>
<rect key="frame" x="527" y="8" width="56.5" height="24"/>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="DE.png" translatesAutoresizingMaskIntoConstraints="NO" id="szG-Mg-huO">
<rect key="frame" x="495.5" y="8" width="24" height="24"/>
<rect key="frame" x="495" y="8" width="24" height="24"/>
<constraints>
<constraint firstAttribute="width" constant="24" id="Ina-uK-atY"/>
<constraint firstAttribute="height" constant="24" id="TVX-WF-dOi"/>
@@ -434,12 +457,12 @@
<rect key="frame" x="255" y="194" width="90" height="60"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="24" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gaG-Iv-Xyq">
<rect key="frame" x="24.5" y="0.0" width="41.5" height="49.5"/>
<rect key="frame" x="24" y="0.0" width="41.5" height="49.5"/>
<fontDescription key="fontDescription" name="OpenSans-Semibold" family="Open Sans" pointSize="36"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Hours" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c2J-Uj-KEg">
<rect key="frame" x="31.5" y="46" width="28.5" height="14"/>
<rect key="frame" x="31" y="46" width="28.5" height="14"/>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="10"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
@@ -471,7 +494,7 @@
<rect key="frame" x="353" y="194" width="90" height="60"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="59" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Txm-uf-MP4">
<rect key="frame" x="24.5" y="0.0" width="41.5" height="49.5"/>
<rect key="frame" x="24" y="0.0" width="41.5" height="49.5"/>
<fontDescription key="fontDescription" name="OpenSans-Semibold" family="Open Sans" pointSize="36"/>
<nil key="highlightedColor"/>
</label>
@@ -513,11 +536,11 @@
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="P9e-ZM-HZP">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="P9e-ZM-HZP">
<rect key="frame" x="303" y="372" width="277" height="44"/>
<color key="backgroundColor" red="0.93725490199999995" green="0.67843137249999996" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" id="jqD-cx-gQM"/>
<constraint firstAttribute="height" constant="44" id="jqD-cx-gQM"/>
</constraints>
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
<state key="normal" title="Leader Board">
@@ -566,7 +589,7 @@
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="13"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QHA-RX-lHP" userLabel="Spacer">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QHA-RX-lHP" userLabel="Spacer">
<rect key="frame" x="299" y="389" width="2" height="10"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
@@ -574,7 +597,7 @@
<constraint firstAttribute="width" constant="2" id="mpd-5K-GxQ"/>
</constraints>
</view>
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Lzv-E6-gsr">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Lzv-E6-gsr">
<rect key="frame" x="20" y="372" width="277" height="44"/>
<color key="backgroundColor" red="0.10980392156862745" green="0.48627450980392156" blue="0.76078431372549016" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
@@ -592,6 +615,35 @@
<action selector="showEvent:" destination="G3a-SV-Maf" eventType="touchUpInside" id="aOq-oB-vIG"/>
</connections>
</button>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Gjq-vj-gAR">
<rect key="frame" x="170" y="48" width="260" height="33"/>
<color key="backgroundColor" white="1" alpha="0.5" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="width" constant="260" id="5jL-oY-VM2"/>
<constraint firstAttribute="height" constant="33" id="ZOg-nP-2k3"/>
</constraints>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="13"/>
<inset key="imageEdgeInsets" minX="5" minY="5" maxX="20" maxY="5"/>
<state key="normal" title="Error during upload. Tap to retry." image="retry">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="2"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="layer.borderWidth">
<real key="value" value="0.5"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="color" keyPath="layer.borderUIColor">
<color key="value" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<connections>
<action selector="showImageAlertView:" destination="G3a-SV-Maf" eventType="touchUpInside" id="GzU-Yx-U2c"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
@@ -599,6 +651,7 @@
<constraint firstItem="CDA-ep-bjo" firstAttribute="top" secondItem="Ezh-Sq-hsb" secondAttribute="top" constant="48" id="3xV-Px-tvy"/>
<constraint firstItem="gHJ-am-Ulb" firstAttribute="top" secondItem="5is-0I-Kv3" secondAttribute="bottom" constant="8" symbolic="YES" id="4Ai-mf-Ab3"/>
<constraint firstItem="ft9-jD-C5m" firstAttribute="trailing" secondItem="gHJ-am-Ulb" secondAttribute="trailing" id="4rt-0W-Jpl"/>
<constraint firstItem="Gjq-vj-gAR" firstAttribute="centerX" secondItem="Ezh-Sq-hsb" secondAttribute="centerX" id="6Zd-Lr-JJ2"/>
<constraint firstItem="mj9-m1-nYY" firstAttribute="trailing" secondItem="gHJ-am-Ulb" secondAttribute="trailing" id="6bk-OB-IQS"/>
<constraint firstItem="pjH-rV-MGI" firstAttribute="top" secondItem="ft9-jD-C5m" secondAttribute="bottom" constant="8" symbolic="YES" id="6va-QB-QGA"/>
<constraint firstItem="8aO-2n-aG5" firstAttribute="bottom" secondItem="cNo-41-lUw" secondAttribute="top" id="716-9b-eFO"/>
@@ -621,6 +674,7 @@
<constraint firstItem="8aO-2n-aG5" firstAttribute="top" secondItem="mj9-m1-nYY" secondAttribute="bottom" constant="8" symbolic="YES" id="SZP-ow-Och"/>
<constraint firstItem="P9e-ZM-HZP" firstAttribute="top" relation="greaterThanOrEqual" secondItem="gHJ-am-Ulb" secondAttribute="bottom" constant="8" symbolic="YES" id="VAj-aB-Ezg"/>
<constraint firstItem="tvc-eS-Us6" firstAttribute="leading" secondItem="5is-0I-Kv3" secondAttribute="trailing" constant="8" id="WJd-lR-8UH"/>
<constraint firstItem="Gjq-vj-gAR" firstAttribute="top" secondItem="wYi-P2-UTF" secondAttribute="bottom" constant="48" id="XQn-se-otI"/>
<constraint firstItem="Lzv-E6-gsr" firstAttribute="leading" secondItem="mj9-m1-nYY" secondAttribute="leading" id="XXL-Ep-csv"/>
<constraint firstItem="5is-0I-Kv3" firstAttribute="top" secondItem="ft9-jD-C5m" secondAttribute="bottom" constant="8" symbolic="YES" id="YvG-vU-Ja4"/>
<constraint firstItem="Ik1-Z0-TGp" firstAttribute="leading" secondItem="Ezh-Sq-hsb" secondAttribute="leading" id="dUq-Dh-9sc"/>
@@ -669,6 +723,7 @@
<outlet property="nameLabel" destination="oJS-dM-2B6" id="fXn-FX-UbJ"/>
<outlet property="openEventButton" destination="Lzv-E6-gsr" id="I7S-26-Qp6"/>
<outlet property="regattaStartLabel" destination="ft9-jD-C5m" id="C70-iD-pB3"/>
<outlet property="retryUploadTeamPhotoButton" destination="Gjq-vj-gAR" id="AJd-4h-9Cz"/>
<outlet property="sailLabel" destination="iIf-9z-7fe" id="xPM-y9-Tjg"/>
<outlet property="startTrackingButton" destination="mj9-m1-nYY" id="eUX-Yd-aix"/>
<outlet property="yourTeamPhotoButton" destination="CDA-ep-bjo" id="15b-r4-Mve"/>
@@ -728,7 +783,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Status" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="r6d-OU-cRI">
<rect key="frame" x="20" y="39.5" width="49.5" height="23.5"/>
<rect key="frame" x="20" y="39" width="49.5" height="23.5"/>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
@@ -740,7 +795,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Tracking Running" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="5Xu-6g-tqP">
<rect key="frame" x="444" y="39.5" width="136" height="23.5"/>
<rect key="frame" x="444" y="39" width="136" height="23.5"/>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
<color key="textColor" red="0.25098039220000001" green="0.50196078430000002" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@@ -748,9 +803,7 @@
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Cached Fixes" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="Ujw-jg-ilY">
<rect key="frame" x="20" y="71" width="102.5" height="24"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="aFo-6u-YNo">
<variation key="heightClass=compact" constant="0.0"/>
</constraint>
<constraint firstAttribute="height" constant="24" id="aFo-6u-YNo"/>
</constraints>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@@ -759,21 +812,19 @@
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="dz3-w7-CCh">
<rect key="frame" x="570" y="71" width="10" height="24"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="itl-Pi-yYx">
<variation key="heightClass=compact" constant="0.0"/>
</constraint>
<constraint firstAttribute="height" constant="24" id="itl-Pi-yYx"/>
</constraints>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
<nil key="highlightedColor"/>
</label>
<containerView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="UcT-H2-Fxp">
<rect key="frame" x="0.0" y="134.5" width="600" height="401.5"/>
<rect key="frame" x="0.0" y="134" width="600" height="401.5"/>
<connections>
<segue destination="rjH-vb-OUF" kind="embed" id="ln5-SR-WkO"/>
</connections>
</containerView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="no GPS" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DiM-MR-SGV">
<rect key="frame" x="506.5" y="8" width="57.5" height="23.5"/>
<rect key="frame" x="506" y="8" width="57.5" height="23.5"/>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
@@ -787,19 +838,41 @@
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Online" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="tJY-RR-vpe">
<rect key="frame" x="527.5" y="103" width="52.5" height="23.5"/>
<rect key="frame" x="527" y="103" width="52.5" height="23.5"/>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
<color key="textColor" red="0.25098040700000002" green="0.50196081400000003" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mnl-aL-6R2">
<rect key="frame" x="20" y="424" width="560" height="75"/>
<color key="backgroundColor" red="0.87843137250000003" green="0.20000000000000001" blue="0.090196078430000007" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="75" id="NVa-wY-Jve">
<variation key="heightClass=compact" constant="50"/>
</constraint>
</constraints>
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
<state key="normal" title="Stop Tracking">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="5"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="stopTrackingButtonTapped:" destination="eHd-jN-WkS" eventType="touchUpInside" id="V6I-ta-wMI"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailingMargin" secondItem="tJY-RR-vpe" secondAttribute="trailing" id="4KI-fl-gq3"/>
<constraint firstAttribute="trailingMargin" secondItem="dz3-w7-CCh" secondAttribute="trailing" id="5YC-rH-Z2d"/>
<constraint firstItem="Ujw-jg-ilY" firstAttribute="top" secondItem="r6d-OU-cRI" secondAttribute="bottom" constant="8" symbolic="YES" id="76Y-1b-oFJ">
<variation key="heightClass=compact" constant="0.0"/>
</constraint>
<constraint firstItem="Ujw-jg-ilY" firstAttribute="top" secondItem="r6d-OU-cRI" secondAttribute="bottom" constant="8" symbolic="YES" id="76Y-1b-oFJ"/>
<constraint firstAttribute="trailingMargin" secondItem="mnl-aL-6R2" secondAttribute="trailing" id="8vV-66-vsk"/>
<constraint firstItem="UcT-H2-Fxp" firstAttribute="top" secondItem="Xww-rc-pOA" secondAttribute="bottom" constant="8" symbolic="YES" id="9el-UI-bzr"/>
<constraint firstItem="UcT-H2-Fxp" firstAttribute="bottom" secondItem="nE0-bK-d8x" secondAttribute="top" id="C25-aA-8XZ"/>
<constraint firstItem="r6d-OU-cRI" firstAttribute="top" secondItem="Z1d-5I-pbj" secondAttribute="bottom" constant="8" symbolic="YES" id="C3i-US-IRf"/>
@@ -810,6 +883,7 @@
<constraint firstItem="Xww-rc-pOA" firstAttribute="leading" secondItem="6kF-Wb-Hv2" secondAttribute="leadingMargin" id="JQs-RK-ocI"/>
<constraint firstItem="r6d-OU-cRI" firstAttribute="leading" secondItem="Z1d-5I-pbj" secondAttribute="leading" id="L9y-4I-Xx1"/>
<constraint firstItem="Ujw-jg-ilY" firstAttribute="leading" secondItem="6kF-Wb-Hv2" secondAttribute="leadingMargin" id="MH7-cy-19A"/>
<constraint firstItem="nE0-bK-d8x" firstAttribute="top" secondItem="mnl-aL-6R2" secondAttribute="bottom" constant="37" id="N0D-rv-8NW"/>
<constraint firstItem="dz3-w7-CCh" firstAttribute="centerY" secondItem="Ujw-jg-ilY" secondAttribute="centerY" id="NPU-zR-WBN"/>
<constraint firstItem="Xww-rc-pOA" firstAttribute="centerY" secondItem="tJY-RR-vpe" secondAttribute="centerY" id="NVc-vI-Vxb"/>
<constraint firstItem="Xww-rc-pOA" firstAttribute="top" secondItem="Ujw-jg-ilY" secondAttribute="bottom" constant="8" symbolic="YES" id="Owh-AZ-oxm"/>
@@ -819,6 +893,7 @@
<constraint firstAttribute="trailingMargin" secondItem="5Xu-6g-tqP" secondAttribute="trailing" id="j12-j8-u1B"/>
<constraint firstItem="Z1d-5I-pbj" firstAttribute="top" secondItem="qL4-PH-8ve" secondAttribute="bottom" constant="8" symbolic="YES" id="pnF-1D-Rxo"/>
<constraint firstAttribute="trailing" secondItem="BFc-m8-XKV" secondAttribute="trailing" constant="16" id="r8t-UX-UKm"/>
<constraint firstItem="mnl-aL-6R2" firstAttribute="leading" secondItem="6kF-Wb-Hv2" secondAttribute="leadingMargin" id="zei-4r-UQH"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Tracking" id="qfB-FT-Ysz">
@@ -854,9 +929,7 @@
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Tracking Time" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="IeZ-JM-OWw">
<rect key="frame" x="20" y="0.0" width="109.5" height="24"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="HVj-ZO-f4Y">
<variation key="heightClass=compact" constant="0.0"/>
</constraint>
<constraint firstAttribute="height" constant="24" id="HVj-ZO-f4Y"/>
</constraints>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@@ -868,41 +941,14 @@
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ULl-vy-5gb">
<rect key="frame" x="20" y="525" width="560" height="75"/>
<color key="backgroundColor" red="0.87843137250000003" green="0.20000000000000001" blue="0.090196078430000007" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="75" id="WnL-zt-cEe">
<variation key="heightClass=compact" constant="50"/>
</constraint>
</constraints>
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
<state key="normal" title="Stop Tracking">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="5"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="stopTrackingButtonTapped:" destination="QJL-mi-hJE" eventType="touchUpInside" id="5fq-CB-McA"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="centerY" secondItem="mUm-EE-6Is" secondAttribute="centerY" priority="750" id="8N8-eA-npB"/>
<constraint firstAttribute="centerX" secondItem="mUm-EE-6Is" secondAttribute="centerX" id="8mv-fJ-KnI"/>
<constraint firstAttribute="centerX" secondItem="ULl-vy-5gb" secondAttribute="centerX" id="Fzl-H6-UnL"/>
<constraint firstItem="IeZ-JM-OWw" firstAttribute="top" secondItem="mP6-V4-QPK" secondAttribute="top" id="IZk-R3-x2F"/>
<constraint firstItem="ULl-vy-5gb" firstAttribute="bottom" secondItem="gKu-YN-ryh" secondAttribute="top" id="UN0-98-lxt"/>
<constraint firstAttribute="trailing" secondItem="mUm-EE-6Is" secondAttribute="trailing" constant="20" symbolic="YES" id="ZKy-Zc-RiE"/>
<constraint firstItem="IeZ-JM-OWw" firstAttribute="leading" secondItem="ULl-vy-5gb" secondAttribute="leading" id="eGE-uB-OHk"/>
<constraint firstItem="IeZ-JM-OWw" firstAttribute="leading" secondItem="mP6-V4-QPK" secondAttribute="leadingMargin" id="eYA-zH-AXy"/>
<constraint firstItem="mUm-EE-6Is" firstAttribute="leading" secondItem="mP6-V4-QPK" secondAttribute="leading" constant="20" symbolic="YES" id="lbB-rJ-KNj"/>
<constraint firstItem="ULl-vy-5gb" firstAttribute="top" relation="greaterThanOrEqual" secondItem="mUm-EE-6Is" secondAttribute="bottom" constant="8" symbolic="YES" id="xXT-VC-dgn"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Tracking" id="fyS-rY-bYp"/>
@@ -934,6 +980,9 @@
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Speed" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="V9h-qX-rao">
<rect key="frame" x="20" y="0.0" width="49.5" height="23.5"/>
<constraints>
<constraint firstAttribute="height" constant="23.5" id="lKq-NM-hVD"/>
</constraints>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
@@ -1008,85 +1057,219 @@
<rect key="frame" x="0.0" y="64" width="600" height="536"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="24z-mh-txV">
<rect key="frame" x="20" y="198" width="560" height="140"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8Vc-YY-1rh">
<rect key="frame" x="0.0" y="80" width="560" height="60"/>
<color key="backgroundColor" red="0.93725490196078431" green="0.67843137254901964" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="60" id="lFu-3n-8uz"/>
</constraints>
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
<state key="normal" title="EULA">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" red="0.5725490196078431" green="0.5725490196078431" blue="0.5725490196078431" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="5"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="openEULA:" destination="WMr-pC-C12" eventType="touchUpInside" id="PhA-bf-yMV"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MpZ-GD-6aE">
<rect key="frame" x="0.0" y="0.0" width="560" height="60"/>
<color key="backgroundColor" red="0.93725490196078431" green="0.67843137254901964" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="60" id="xSB-Dr-qVN"/>
</constraints>
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
<state key="normal" title="License Information">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" red="0.5725490196078431" green="0.5725490196078431" blue="0.5725490196078431" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="5"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
</userDefinedRuntimeAttributes>
<connections>
<segue destination="awJ-L8-hjX" kind="show" id="0cD-Vt-sEk"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="8Vc-YY-1rh" secondAttribute="trailing" id="0ns-WL-guw"/>
<constraint firstItem="MpZ-GD-6aE" firstAttribute="top" secondItem="24z-mh-txV" secondAttribute="top" id="3z0-uD-NWx"/>
<constraint firstItem="MpZ-GD-6aE" firstAttribute="leading" secondItem="24z-mh-txV" secondAttribute="leading" id="CgF-IV-BDx"/>
<constraint firstItem="8Vc-YY-1rh" firstAttribute="top" secondItem="MpZ-GD-6aE" secondAttribute="bottom" constant="20" id="KgN-wG-9Pf"/>
<constraint firstAttribute="bottom" secondItem="8Vc-YY-1rh" secondAttribute="bottom" id="XB1-I9-hVG"/>
<constraint firstAttribute="trailing" secondItem="MpZ-GD-6aE" secondAttribute="trailing" id="rvL-Ze-CNy"/>
<constraint firstItem="8Vc-YY-1rh" firstAttribute="leading" secondItem="24z-mh-txV" secondAttribute="leading" id="sv6-8L-lF0"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Version" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YpQ-wa-mW3">
<rect key="frame" x="20" y="346" width="45.5" height="18"/>
<rect key="frame" x="20" y="484" width="45.5" height="18"/>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="13"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1.0.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jsi-LA-7jU">
<rect key="frame" x="20" y="372" width="29.5" height="18"/>
<rect key="frame" x="20" y="510" width="29.5" height="18"/>
<fontDescription key="fontDescription" name="OpenSans" family="Open Sans" pointSize="13"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8Vc-YY-1rh">
<rect key="frame" x="20" y="416" width="560" height="60"/>
<color key="backgroundColor" red="0.93725490196078431" green="0.67843137254901964" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="60" id="lFu-3n-8uz"/>
</constraints>
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
<state key="normal" title="EULA">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" red="0.5725490196078431" green="0.5725490196078431" blue="0.5725490196078431" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="5"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="openEULA:" destination="WMr-pC-C12" eventType="touchUpInside" id="PhA-bf-yMV"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MpZ-GD-6aE">
<rect key="frame" x="20" y="340" width="560" height="60"/>
<color key="backgroundColor" red="0.93725490196078431" green="0.67843137254901964" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="60" id="xSB-Dr-qVN"/>
</constraints>
<fontDescription key="fontDescription" name="OpenSans-Bold" family="Open Sans" pointSize="15"/>
<state key="normal" title="License Information">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" red="0.5725490196078431" green="0.5725490196078431" blue="0.5725490196078431" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="5"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
</userDefinedRuntimeAttributes>
<connections>
<segue destination="awJ-L8-hjX" kind="show" id="0cD-Vt-sEk"/>
</connections>
</button>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" editable="NO" usesAttributedText="YES" translatesAutoresizingMaskIntoConstraints="NO" id="yEk-7o-5Ua">
<rect key="frame" x="20" y="20" width="560" height="300"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<attributedString key="attributedText">
<fragment>
<string key="content">The SAP Sail InSight app supports live tracking of race boats by leveraging mobile phone technology, allowing the ability for friends and fans to follow racing whether onsite or online through live leader boards and 2D visualization. Following racing sailors can replay their races allowing the opportunity to review performance and optimize strategies all available at: </string>
<attributes>
<color key="NSColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
<font key="NSFont" size="16" name="Calibri"/>
<real key="NSKern" value="0.0"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
<tabStops/>
</paragraphStyle>
<color key="NSStrokeColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
</attributes>
</fragment>
<fragment content="sapsailing.com">
<attributes>
<color key="NSColor" red="0.2196078431372549" green="0.43137254901960786" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<font key="NSFont" size="16" name="Calibri"/>
<real key="NSKern" value="0.0"/>
<url key="NSLink" string="http://sapsailing.com/"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
<tabStops/>
</paragraphStyle>
<color key="NSStrokeColor" red="0.2196078431372549" green="0.43137254901960786" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<integer key="NSUnderline" value="1"/>
<color key="NSUnderlineColor" red="0.2196078431372549" green="0.43137254901960786" blue="1" alpha="1" colorSpace="calibratedRGB"/>
</attributes>
</fragment>
<fragment content="   ">
<attributes>
<color key="NSColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
<font key="NSFont" size="16" name="Calibri"/>
<real key="NSKern" value="0.0"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
<tabStops/>
</paragraphStyle>
<color key="NSStrokeColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
</attributes>
</fragment>
<fragment>
<string key="content" base64-UTF8="YES">
Cg
</string>
<attributes>
<font key="NSFont" size="16" name="TimesNewRomanPSMT"/>
<real key="NSKern" value="0.0"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
<tabStops/>
</paragraphStyle>
<color key="NSStrokeColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</attributes>
</fragment>
<fragment content=" ">
<attributes>
<color key="NSColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
<font key="NSFont" size="16" name="Calibri"/>
<real key="NSKern" value="0.0"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
<tabStops/>
</paragraphStyle>
<color key="NSStrokeColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
</attributes>
</fragment>
<fragment>
<string key="content" base64-UTF8="YES">
Cg
</string>
<attributes>
<font key="NSFont" size="16" name="TimesNewRomanPSMT"/>
<real key="NSKern" value="0.0"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
<tabStops/>
</paragraphStyle>
<color key="NSStrokeColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</attributes>
</fragment>
<fragment>
<string key="content">SAP supports US Sailing through the implementation of a range of innovative technology solutions, delivering analytics to help teams and sailors improve performance whilst also providing applications to help sailing operations run more efficiently.</string>
<attributes>
<color key="NSColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
<font key="NSFont" size="16" name="Calibri"/>
<real key="NSKern" value="0.0"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
<tabStops/>
</paragraphStyle>
<color key="NSStrokeColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
</attributes>
</fragment>
<fragment>
<string key="content" base64-UTF8="YES">
Cg
</string>
<attributes>
<font key="NSFont" size="16" name="TimesNewRomanPSMT"/>
<real key="NSKern" value="0.0"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
<tabStops/>
</paragraphStyle>
<color key="NSStrokeColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</attributes>
</fragment>
<fragment content=" ">
<attributes>
<color key="NSColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
<font key="NSFont" size="16" name="Calibri"/>
<real key="NSKern" value="0.0"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
<tabStops/>
</paragraphStyle>
<color key="NSStrokeColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
</attributes>
</fragment>
<fragment>
<string key="content" base64-UTF8="YES">
Cg
</string>
<attributes>
<font key="NSFont" size="16" name="TimesNewRomanPSMT"/>
<real key="NSKern" value="0.0"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
<tabStops/>
</paragraphStyle>
<color key="NSStrokeColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</attributes>
</fragment>
<fragment>
<string key="content">As the national governing body for sailing, US Sailing provides leadership, integrity and growth for the sport including training and education programs for instructors and race officials alongside administration and oversight of competitive sailing across the country.</string>
<attributes>
<color key="NSColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
<font key="NSFont" size="16" name="Calibri"/>
<real key="NSKern" value="0.0"/>
<paragraphStyle key="NSParagraphStyle" alignment="left" lineBreakMode="wordWrapping" baseWritingDirection="leftToRight" defaultTabInterval="36" tighteningFactorForTruncation="0.0" allowsDefaultTighteningForTruncation="NO">
<tabStops/>
</paragraphStyle>
<color key="NSStrokeColor" red="0.094117647058823528" green="0.21568627450980393" blue="0.41568627450980394" alpha="1" colorSpace="calibratedRGB"/>
</attributes>
</fragment>
</attributedString>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" link="YES"/>
</textView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="yEk-7o-5Ua" firstAttribute="trailing" secondItem="wZ4-L7-UtM" secondAttribute="trailingMargin" id="6Pr-e7-RK2"/>
<constraint firstItem="8Vc-YY-1rh" firstAttribute="leading" secondItem="wZ4-L7-UtM" secondAttribute="leadingMargin" id="Ccw-Sx-67a"/>
<constraint firstItem="Jsi-LA-7jU" firstAttribute="top" secondItem="YpQ-wa-mW3" secondAttribute="bottom" constant="8" id="Cj5-RI-puj"/>
<constraint firstItem="24z-mh-txV" firstAttribute="centerY" secondItem="wZ4-L7-UtM" secondAttribute="centerY" id="ETl-XU-Jke"/>
<constraint firstItem="24z-mh-txV" firstAttribute="centerX" secondItem="wZ4-L7-UtM" secondAttribute="centerX" id="Qeq-G6-dez"/>
<constraint firstAttribute="trailingMargin" secondItem="24z-mh-txV" secondAttribute="trailing" id="Rl3-ut-OsJ"/>
<constraint firstItem="UkO-Wl-zJn" firstAttribute="top" secondItem="Jsi-LA-7jU" secondAttribute="bottom" constant="8" id="CyR-gx-2J2"/>
<constraint firstItem="MpZ-GD-6aE" firstAttribute="leading" secondItem="wZ4-L7-UtM" secondAttribute="leadingMargin" id="EG0-ma-ckk"/>
<constraint firstItem="yEk-7o-5Ua" firstAttribute="top" secondItem="TdH-NN-ozb" secondAttribute="bottom" constant="20" id="HGZ-ua-ZM2"/>
<constraint firstItem="yEk-7o-5Ua" firstAttribute="leading" secondItem="wZ4-L7-UtM" secondAttribute="leadingMargin" id="JKk-Ew-9V1"/>
<constraint firstItem="8Vc-YY-1rh" firstAttribute="top" secondItem="MpZ-GD-6aE" secondAttribute="bottom" constant="16" id="Jy6-Nc-7Jc"/>
<constraint firstItem="Jsi-LA-7jU" firstAttribute="leading" secondItem="wZ4-L7-UtM" secondAttribute="leadingMargin" id="SgV-Fo-88q"/>
<constraint firstItem="YpQ-wa-mW3" firstAttribute="top" secondItem="24z-mh-txV" secondAttribute="bottom" constant="8" id="igG-9X-MdL"/>
<constraint firstItem="24z-mh-txV" firstAttribute="leading" secondItem="wZ4-L7-UtM" secondAttribute="leadingMargin" id="tz7-OQ-537"/>
<constraint firstItem="MpZ-GD-6aE" firstAttribute="top" secondItem="yEk-7o-5Ua" secondAttribute="bottom" constant="20" id="bfl-MB-ii4"/>
<constraint firstItem="YpQ-wa-mW3" firstAttribute="top" secondItem="8Vc-YY-1rh" secondAttribute="bottom" constant="8" id="fR3-Gc-K7V"/>
<constraint firstItem="MpZ-GD-6aE" firstAttribute="trailing" secondItem="wZ4-L7-UtM" secondAttribute="trailingMargin" id="fRY-RC-lgO"/>
<constraint firstItem="8Vc-YY-1rh" firstAttribute="trailing" secondItem="wZ4-L7-UtM" secondAttribute="trailingMargin" id="gxG-bx-dTv"/>
<constraint firstItem="YpQ-wa-mW3" firstAttribute="leading" secondItem="wZ4-L7-UtM" secondAttribute="leadingMargin" id="wev-ws-P1D"/>
</constraints>
</view>
@@ -1098,6 +1281,7 @@
</barButtonItem>
</navigationItem>
<connections>
<outlet property="textView" destination="yEk-7o-5Ua" id="a7p-wJ-DDg"/>
<outlet property="versionDescriptionLabel" destination="YpQ-wa-mW3" id="ndm-Jp-4j0"/>
<outlet property="versionLabel" destination="Jsi-LA-7jU" id="b1V-A3-5jG"/>
</connections>
@@ -1360,15 +1544,17 @@
</scenes>
<resources>
<image name="DE.png" width="64" height="64"/>
<image name="applogo" width="365" height="96"/>
<image name="camera" width="24" height="24"/>
<image name="edit" width="33" height="33"/>
<image name="home_bg" width="980" height="653"/>
<image name="profile_placeholder" width="360" height="256"/>
<image name="retry" width="24" height="24"/>
<image name="scan_white" width="214" height="214"/>
<image name="settings" width="22" height="22"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="QMa-LQ-D8c"/>
<segue reference="Xxn-jp-xEB"/>
<segue reference="tzB-PL-31j"/>
<segue reference="Irs-ey-Uhs"/>
</inferredMetricsTieBreakers>
</document>
+82 -18
View File
@@ -9,7 +9,7 @@
import UIKit
import CoreData
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIActionSheetDelegate, NSFetchedResultsControllerDelegate, QRCodeManagerDelegate {
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate, QRCodeManagerDelegate {
enum AlertView: Int {
case NoCameraAvailable
@@ -25,12 +25,15 @@ class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDe
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var tableViewHeight: NSLayoutConstraint!
@IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
@IBOutlet var vSplashScreen: UIView!
var fetchedResultsController: NSFetchedResultsController?
private var qrCodeManager: QRCodeManager?
override func viewDidLoad() {
super.viewDidLoad()
loadSplashScreen()
// set QR manager, needed in case app is being open by custom URL
qrCodeManager = QRCodeManager(delegate: self)
@@ -61,6 +64,50 @@ class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDe
let barButtonItem = UIBarButtonItem(customView: imageView)
navigationItem.leftBarButtonItem = barButtonItem
}
func loadSplashScreen() {
if let keyWindow = UIApplication.sharedApplication().keyWindow {
vSplashScreen.translatesAutoresizingMaskIntoConstraints = false
keyWindow.addSubview(vSplashScreen)
let lcTop = NSLayoutConstraint(item: vSplashScreen,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: keyWindow,
attribute: NSLayoutAttribute.Top,
multiplier: 1.0,
constant: 0)
let lcBottom = NSLayoutConstraint(item: vSplashScreen,
attribute: NSLayoutAttribute.Bottom,
relatedBy: NSLayoutRelation.Equal,
toItem: keyWindow,
attribute: NSLayoutAttribute.Bottom,
multiplier: 1.0,
constant: 0)
let lcLeft = NSLayoutConstraint(item: vSplashScreen,
attribute: NSLayoutAttribute.Left,
relatedBy: NSLayoutRelation.Equal,
toItem: keyWindow,
attribute: NSLayoutAttribute.Left,
multiplier: 1.0,
constant: 0)
let lcRight = NSLayoutConstraint(item: vSplashScreen,
attribute: NSLayoutAttribute.Right,
relatedBy: NSLayoutRelation.Equal,
toItem: keyWindow,
attribute: NSLayoutAttribute.Right,
multiplier: 1.0,
constant: 0)
keyWindow.addConstraints([lcTop, lcBottom, lcLeft, lcRight])
weak var weakself = self
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
weakself?.vSplashScreen.removeFromSuperview()
}
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
@@ -103,26 +150,39 @@ class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDe
let url = notification.userInfo!["url"] as! String
qrCodeManager!.parseUrl(url)
}
// MARK: - UIActionSheetDelegate
func _DEBUG_OPEN_URL() {
let url = "http://ec2-54-171-89-140.eu-west-1.compute.amazonaws.com:8888/tracking/checkin?event_id=71c7b531-fb1b-441c-b2fa-f4e9ff672d60&leaderboard_name=Ubigatta&competitor_id=9df7b4f6-611b-4be0-b028-c7b1bdd434c2"
let url2 = "http://ec2-54-171-89-140.eu-west-1.compute.amazonaws.com:8888/tracking/checkin?event_id=71c7b531-fb1b-441c-b2fa-f4e9ff672d60&leaderboard_name=Ubigatta&competitor_id=a5a00800-daf8-0131-89e6-60a44ce903c3"
qrCodeManager!.parseUrl(url2)
}
@IBAction func showActionSheet(sender: AnyObject) {
let actionSheet = UIActionSheet(title: nil, delegate: self, cancelButtonTitle: nil, destructiveButtonTitle: nil, otherButtonTitles: NSLocalizedString("Settings", comment: ""), NSLocalizedString("About", comment: ""), NSLocalizedString("Cancel", comment: ""))
actionSheet.cancelButtonIndex = 3
actionSheet.showInView(view)
}
func actionSheet(actionSheet: UIActionSheet, clickedButtonAtIndex buttonIndex: Int) {
switch buttonIndex{
case 0:
performSegueWithIdentifier("Settings", sender: actionSheet)
break
case 1:
performSegueWithIdentifier("About", sender: actionSheet)
break
default:
break
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
if let popoverController = actionSheet.popoverPresentationController,
let barButtonItem = sender as? UIBarButtonItem {
popoverController.barButtonItem = barButtonItem
}
let aSettings = UIAlertAction(title: NSLocalizedString("Settings", comment: ""), style: .Default) { (action) -> Void in
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.6 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
self.performSegueWithIdentifier("SettingsFromHome", sender: actionSheet)
}
}
actionSheet.addAction(aSettings)
let aAbout = UIAlertAction(title: NSLocalizedString("About", comment: ""), style: .Default) { (action) -> Void in
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.6 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
self.performSegueWithIdentifier("AboutFromHome", sender: actionSheet)
}
}
actionSheet.addAction(aAbout)
let aCancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .Cancel, handler: nil)
actionSheet.addAction(aCancel)
presentViewController(actionSheet, animated: true, completion: nil)
}
// MARK: - UITableViewDataSource
@@ -208,6 +268,10 @@ class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDe
// MARK: - Button actions
@IBAction func scanButtonTap(sender: AnyObject) {
// _DEBUG_OPEN_URL()
// return;
if !UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) {
let alertView = UIAlertView(title: NSLocalizedString("No camera available.", comment: ""), message: nil, delegate: nil, cancelButtonTitle: NSLocalizedString("Cancel", comment: ""))
alertView.tag = AlertView.NoCameraAvailable.rawValue;
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -73,8 +73,9 @@
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "167_iPhone_6Plus_App_Icon@2x.png",
"scale" : "2x"
}
],
@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "applogo@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "applogo@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "retry@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

@@ -6,8 +6,8 @@
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "sap_logo.png"
"filename" : "sap_logo@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>SAP Sail inSight</string>
<string>SAP Sail InSight</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@@ -30,7 +30,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>27</string>
<string>37</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
@@ -51,6 +51,8 @@
<array>
<string>location</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9532" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="applogo" translatesAutoresizingMaskIntoConstraints="NO" id="XA8-DM-z3j">
<rect key="frame" x="117.5" y="252" width="365" height="96"/>
<constraints>
<constraint firstAttribute="width" secondItem="XA8-DM-z3j" secondAttribute="height" multiplier="365:96" id="nLx-D5-TPO"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="XA8-DM-z3j" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4Bv-Zq-ZzP"/>
<constraint firstItem="XA8-DM-z3j" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Ze5-6b-2t3" secondAttribute="leading" constant="30" id="EFm-df-I71"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="XA8-DM-z3j" secondAttribute="trailing" constant="30" id="KiK-A3-oaY"/>
<constraint firstItem="XA8-DM-z3j" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="TsS-GE-Hlz"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="applogo" width="365" height="96"/>
</resources>
</document>
@@ -45,6 +45,14 @@ class LocationManager: NSObject, CLLocationManagerDelegate {
if (coreLocationManager.respondsToSelector("requestAlwaysAuthorization")) {
coreLocationManager.requestAlwaysAuthorization()
}
// Now try everything to allow the app to use the GPS sensor
// while in background
coreLocationManager.pausesLocationUpdatesAutomatically = false;
if #available(iOS 9, *) {
coreLocationManager.allowsBackgroundLocationUpdates = true;
}
coreLocationManager.startUpdatingLocation()
coreLocationManager.startUpdatingHeading()
coreLocationManager.delegate = self
@@ -102,4 +110,4 @@ class LocationManager: NSObject, CLLocationManagerDelegate {
return true;
}
}
}
@@ -96,17 +96,17 @@ class QRCodeManager : NSObject, UIAlertViewDelegate {
// get event
APIManager.sharedManager.getEvent(qrcodeData!.eventId,
success: { (AFHTTPRequestOperation operation, AnyObject eventResponseObject) -> Void in
success: { (operation, eventResponseObject) -> Void in
self.eventDictionary = eventResponseObject as? [String: AnyObject]
APIManager.sharedManager.getLeaderBoard(self.qrcodeData!.leaderBoardName,
// get leader board
success: { (AFHTTPRequestOperation operation, AnyObject leaderBoardResponseObject) -> Void in
success: { (operation, leaderBoardResponseObject) -> Void in
self.leaderBoardDictionary = leaderBoardResponseObject as? [String: AnyObject]
APIManager.sharedManager.getCompetitor(self.qrcodeData!.competitorId,
// get competitor
success: { (AFHTTPRequestOperation operation, AnyObject competitorResponseObject) -> Void in
success: { (operation, competitorResponseObject) -> Void in
APIManager.sharedManager.teamImage(self.qrcodeData!.competitorId, result: { (imageUrl) -> Void in
@@ -125,14 +125,14 @@ class QRCodeManager : NSObject, UIAlertViewDelegate {
alertView.show()
})
}, failure: { (AFHTTPRequestOperation operation, NSError error) -> Void in
}, failure: { (operation, error) -> Void in
self.delegate.activityIndicatorView?.stopAnimating()
let title = String(format: NSLocalizedString("Couldn't get competitor %@", comment: ""), self.qrcodeData!.competitorId!)
let alertView = UIAlertView(title: title, message: error.localizedDescription, delegate: self, cancelButtonTitle: NSLocalizedString("Cancel", comment: ""))
alertView.tag = AlertView.ServerError.rawValue;
alertView.show()
}) }, failure: { (AFHTTPRequestOperation operation, NSError error) -> Void in
}) }, failure: { (operation, error) -> Void in
self.delegate.activityIndicatorView?.stopAnimating()
let title = String(format: NSLocalizedString("Couldn't get leader board %@", comment: ""), self.qrcodeData!.leaderBoardName!)
let alertView = UIAlertView(title: title, message: error.localizedDescription, delegate: self, cancelButtonTitle: NSLocalizedString("Cancel", comment: ""))
@@ -140,7 +140,7 @@ class QRCodeManager : NSObject, UIAlertViewDelegate {
alertView.show()
})
}, failure: { (AFHTTPRequestOperation operation, NSError error) -> Void in
}, failure: { (operation, error) -> Void in
self.delegate.activityIndicatorView?.stopAnimating()
let title = String(format: NSLocalizedString("Couldn't get event %@", comment: ""), self.qrcodeData!.eventId!)
let alertView = UIAlertView(title: title, message: error.localizedDescription, delegate: self, cancelButtonTitle: NSLocalizedString("Cancel", comment: ""))
@@ -185,7 +185,7 @@ class QRCodeManager : NSObject, UIAlertViewDelegate {
checkIn.imageUrl = teamImageURL
APIManager.sharedManager.checkIn(leaderBoardName, competitorId: competitorId, deviceUuid: DeviceUDIDManager.UDID, pushDeviceId: "", fromMillis: fromMillis,
success: { (AFHTTPRequestOperation operation, AnyObject eventResponseObject) -> Void in
success: { (operation, eventResponseObject) -> Void in
// create core data objects
let event = DataManager.sharedManager.newEvent(checkIn)
@@ -197,7 +197,7 @@ class QRCodeManager : NSObject, UIAlertViewDelegate {
DataManager.sharedManager.saveContext()
self.delegate.qrCodeOK?()
}, failure: { (AFHTTPRequestOperation operation, NSError error) -> Void in
}, failure: { (operation, error) -> Void in
self.delegate.activityIndicatorView?.stopAnimating()
let title = String(format:NSLocalizedString("Couldn't check-in to %@", comment: ""), leaderBoardName);
let alertView = UIAlertView(title: title, message: error.localizedDescription, delegate: self, cancelButtonTitle: NSLocalizedString("Cancel", comment: ""))
@@ -18,13 +18,18 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
enum AlertView: Int {
case CheckOut, Image, UploadFailed
}
struct RegattaViewUserDefaults {
static let UploadDidFail = "UploadDidFail"
}
var sourceTypes = [UIImagePickerControllerSourceType]()
var sourceTypeNames = [String]()
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var yourTeamPhotoButton: UIButton!
@IBOutlet weak var editTeamPhotoButton: UIButton!
@IBOutlet weak var retryUploadTeamPhotoButton: UIButton!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var flagImageView: UIImageView!
@IBOutlet weak var sailLabel: UILabel!
@@ -55,6 +60,10 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
dateFormatter.dateFormat = "MMM d, yyyy, h:mm a" // As per request - see mail 19.02.2016
super.init(coder: aDecoder)!
}
var uploadKey: String {
return RegattaViewUserDefaults.UploadDidFail + "_" + DataManager.sharedManager.selectedCheckIn!.competitorId
}
override func viewDidLoad() {
@@ -62,6 +71,8 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
navigationItem.title = DataManager.sharedManager.selectedCheckIn!.leaderBoardName
openEventButton.setTitle(NSLocalizedString("Show Event", comment: ""), forState: UIControlState.Normal)
retryUploadTeamPhotoButton.hidden = !NSUserDefaults.standardUserDefaults().boolForKey(uploadKey)
// set regatta image, either load it from server or load from core data
if DataManager.sharedManager.selectedCheckIn?.userImage != nil {
@@ -78,10 +89,12 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
},
failure: { (request:NSURLRequest!,response:NSHTTPURLResponse!, error:NSError!) -> Void in
self.editTeamPhotoButton.hidden = true
self.yourTeamPhotoButton.hidden = !self.retryUploadTeamPhotoButton.hidden
}
)
} else {
self.editTeamPhotoButton.hidden = true
self.yourTeamPhotoButton.hidden = !self.retryUploadTeamPhotoButton.hidden
}
if (DataManager.sharedManager.selectedCheckIn?.competitor != nil) {
let competitor = DataManager.sharedManager.selectedCheckIn!.competitor!
@@ -211,7 +224,9 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
case 0:
showCheckOutAlertView()
case 1:
performSegueWithIdentifier("Settings", sender: actionSheet)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.6 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
self.performSegueWithIdentifier("Settings", sender: actionSheet)
}
break
case 2:
showImageAlertView(actionSheet)
@@ -261,24 +276,32 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
imageView.image = image
yourTeamPhotoButton.hidden = true
editTeamPhotoButton.hidden = false
retryUploadTeamPhotoButton.hidden = true
let jpegData = UIImageJPEGRepresentation(image, 0.8)
DataManager.sharedManager.selectedCheckIn!.userImage = jpegData
APIManager.sharedManager.postTeamImage(DataManager.sharedManager.selectedCheckIn!.competitorId,
imageData: jpegData,
success: { (AnyObject responseObject) -> Void in
success: { (responseObject) -> Void in
// http://wiki.sapsailing.com/wiki/tracking-app/api-v1#Competitor-Information-%28in-general%29
// "Additional Notes: Competitor profile image left out for now."
let responseDictionary = responseObject as![String: AnyObject]
let imageUrl = (responseDictionary["teamImageUri"]) as! String;
DataManager.sharedManager.selectedCheckIn!.imageUrl = imageUrl;
NSUserDefaults.standardUserDefaults().setBool(false, forKey: self.uploadKey)
NSUserDefaults.standardUserDefaults().synchronize()
},
failure: { (NSError error) -> Void in
failure: { (error) -> Void in
let alertView = UIAlertView(title: NSLocalizedString("Failed to upload image", comment: ""), message: error.localizedDescription, delegate: self, cancelButtonTitle: NSLocalizedString("Cancel", comment: ""))
alertView.tag = AlertView.UploadFailed.rawValue;
alertView.show()
alertView.show()
NSUserDefaults.standardUserDefaults().setBool(true, forKey: self.uploadKey)
NSUserDefaults.standardUserDefaults().synchronize()
self.retryUploadTeamPhotoButton.hidden = false
})
}
// MARK: - Check-out
func showCheckOutAlertView() {
let alertView = UIAlertView(title: NSLocalizedString("Check-out of Regatta?", comment: ""), message: "", delegate: self, cancelButtonTitle: NSLocalizedString("Cancel", comment: ""), otherButtonTitles: NSLocalizedString("OK", comment: ""))
@@ -302,9 +325,9 @@ class RegattaViewController : UIViewController, UIActionSheetDelegate, UINavigat
competitorId: DataManager.sharedManager.selectedCheckIn!.competitorId,
deviceUuid: DeviceUDIDManager.UDID,
toMillis: toMillis,
success: { (AFHTTPRequestOperation operation, AnyObject competitorResponseObject) -> Void in
success: { (operation, competitorResponseObject) -> Void in
},
failure: { (AFHTTPRequestOperation operation, NSError error) -> Void in
failure: { (operation, error) -> Void in
}
)
DataManager.sharedManager.deleteCheckIn(DataManager.sharedManager.selectedCheckIn!)
@@ -80,7 +80,7 @@ class SendGPSFixController: NSObject {
APIManager.sharedManager.initManager(serverUrl)
if APIManager.sharedManager.networkAvailable {
APIManager.sharedManager.postGPSFixes(DeviceUDIDManager.UDID, gpsFixes: gpsFixes,
success: { (AFHTTPRequestOperation operation, AnyObject competitorResponseObject) -> Void in
success: { (operation, competitorResponseObject) -> Void in
print("sent GPS fixes")
for gpsFix in gpsFixes {
DataManager.sharedManager.managedObjectContext!.deleteObject(gpsFix)
@@ -88,7 +88,7 @@ class SendGPSFixController: NSObject {
let notification = NSNotification(name: NotificationType.gpsFixesSynced, object: self)
NSNotificationQueue.defaultQueue().enqueueNotification(notification, postingStyle: NSPostingStyle.PostASAP)
},
failure: { (AFHTTPRequestOperation operation, NSError error) -> Void in
failure: { (operation, error) -> Void in
print("error sending GPS fixes")
})
}
@@ -15,6 +15,11 @@ class SettingsViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
batterySavingSwitch.setOn(BatteryManager.sharedManager.batterySavingPreference, animated: true)
// add logo to top left
let imageView = UIImageView(image: UIImage(named: "sap_logo"))
let barButtonItem = UIBarButtonItem(customView: imageView)
navigationItem.leftBarButtonItem = barButtonItem
}
@IBAction func batterySavingChanged(sender: UISwitch) {
@@ -25,8 +25,12 @@ class SpeedViewController: UIViewController {
func newLocation(notification: NSNotification) {
let ms = notification.userInfo!["speed"] as! Double
let kn = ms * knPerMs
speedLabel.text = String(format: "%0.1f kn", kn)
if(ms >= 0) {
let kn = ms * knPerMs
speedLabel.text = String(format: "%0.1f kn", kn)
} else {
speedLabel.text = "– kn"
}
}
}
@@ -1,34 +0,0 @@
//
// SplashScreenDummy.swift
// SAPTracker
//
// Created by computing on 02/12/14.
// Copyright (c) 2014 com.sap.sailing. All rights reserved.
//
import Foundation
/* Helper class used for created launch screens. */
class SplashScreenDummy: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// add logo to top left
let imageView = UIImageView(image: UIImage(named: "sap_logo"))
let barButtonItem = UIBarButtonItem(customView: imageView)
navigationItem.leftBarButtonItem = barButtonItem
//UIApplication.sharedApplication().statusBarHidden = true
}
}
/* Uncomment this when creating launch screens. */
/*
extension UINavigationBar {
public override func sizeThatFits(size: CGSize) -> CGSize {
let newSize = CGSizeMake(UIScreen.mainScreen().bounds.width, 64)
return newSize
}
}
*/
@@ -8,11 +8,8 @@
import Foundation
class TimerViewController: UIViewController, UIAlertViewDelegate {
enum AlertView: Int {
case StopTracking
}
class TimerViewController: UIViewController {
@IBOutlet weak var trackingTimeLabel: UILabel!
let startDate = NSDate()
@@ -37,33 +34,4 @@ class TimerViewController: UIViewController, UIAlertViewDelegate {
let timerDate = NSDate(timeIntervalSince1970: timeInterval)
trackingTimeLabel.text = dateFormatter.stringFromDate(timerDate)
}
// MARK:- Buttons
/* Stop tracking, go back to regattas view */
@IBAction func stopTrackingButtonTapped(sender: AnyObject) {
let alertView = UIAlertView(title: NSLocalizedString("Stop tracking?", comment: ""), message: "", delegate: self, cancelButtonTitle: NSLocalizedString("Cancel", comment: ""), otherButtonTitles: NSLocalizedString("Stop", comment: ""))
alertView.tag = AlertView.StopTracking.rawValue;
alertView.show()
}
/* Alert view delegate */
func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
switch alertView.tag {
// Stop tracking?
case AlertView.StopTracking.rawValue:
switch buttonIndex {
case alertView.cancelButtonIndex:
break
default:
LocationManager.sharedManager.stopTracking()
SendGPSFixController.sharedManager.checkIn = nil
self.dismissViewControllerAnimated(true, completion: nil)
}
break
default:
break
}
}
}
@@ -39,6 +39,23 @@ class TrackingViewController : UIViewController {
NSNotificationCenter.defaultCenter().addObserver(self, selector:"newLocation:", name:LocationManager.NotificationType.newLocation, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector:"locationManagerFailed:", name:LocationManager.NotificationType.locationManagerFailed, object: nil)
}
// MARK:- Buttons
/* Stop tracking, go back to regattas view */
@IBAction func stopTrackingButtonTapped(sender: AnyObject) {
let alertController = UIAlertController(title: NSLocalizedString("Stop tracking?", comment: ""), message: "", preferredStyle: .Alert)
let aCancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .Cancel, handler: nil)
alertController.addAction(aCancel)
let aStop = UIAlertAction(title: NSLocalizedString("Stop", comment: ""), style: .Default) { action in
LocationManager.sharedManager.stopTracking()
SendGPSFixController.sharedManager.checkIn = nil
self.dismissViewControllerAnimated(true, completion: nil)
}
alertController.addAction(aStop)
presentViewController(alertController, animated: true, completion: nil)
}
// MARK:- Notifications
@@ -112,7 +112,7 @@ public class TracTrac {
raceRecords = getTracTracAdapter().getTracTracRaceRecords(new URL(eventJsonURL), /*loadClientParam*/ false);
List<TracTracRaceRecordDTO> result = new ArrayList<TracTracRaceRecordDTO>();
for (RaceRecord raceRecord : raceRecords.getB()) {
if (listHiddenRaces == false && raceRecord.getRaceStatus().equals(TracTracConnectionConstants.HIDDEN_STATUS)) {
if (listHiddenRaces == false && raceRecord.getRaceVisibility().equals(TracTracConnectionConstants.HIDDEN_VISIBILITY)) {
continue;
}
@@ -0,0 +1,3 @@
eclipse.preferences.version=1
encoding//src/main/java/com/sap/sailing/dashboards/gwt/client/i18n/StringMessages.properties=UTF-8
encoding//src/main/java/com/sap/sailing/dashboards/gwt/client/i18n/StringMessages_de.properties=UTF-8
@@ -1,140 +1,140 @@
package com.sap.sailing.datamining;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.osgi.framework.BundleContext;
import com.sap.sailing.datamining.data.HasGPSFixContext;
import com.sap.sailing.datamining.data.HasLeaderboardContext;
import com.sap.sailing.datamining.data.HasLeaderboardGroupContext;
import com.sap.sailing.datamining.data.HasMarkPassingContext;
import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext;
import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelBoolSumAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceAverageAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMaxAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMedianAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMinAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceSumAggregationProcessor;
import com.sap.sailing.domain.common.Speed;
import com.sap.sse.datamining.DataSourceProvider;
import com.sap.sse.datamining.components.AggregationProcessorDefinition;
import com.sap.sse.datamining.components.DataRetrieverChainDefinition;
import com.sap.sse.datamining.data.ClusterGroup;
import com.sap.sse.datamining.impl.AbstractDataMiningActivatorWithPredefinedQueries;
import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO;
import com.sap.sse.datamining.shared.impl.PredefinedQueryIdentifier;
import com.sap.sse.i18n.ResourceBundleStringMessages;
import com.sap.sse.i18n.impl.ResourceBundleStringMessagesImpl;
public class Activator extends AbstractDataMiningActivatorWithPredefinedQueries {
private static final String STRING_MESSAGES_BASE_NAME = "stringmessages/Sailing_StringMessages";
private static final SailingClusterGroups clusterGroups = new SailingClusterGroups();
private static Activator INSTANCE;
private BundleContext context = null;
private final ResourceBundleStringMessages sailingServerStringMessages;
private final SailingDataRetrievalChainDefinitions dataRetrieverChainDefinitions;
private final SailingPredefinedQueries predefinedQueries;
private Collection<DataSourceProvider<?>> dataSourceProviders;
private boolean dataSourceProvidersHaveBeenInitialized;
public Activator() {
sailingServerStringMessages = new ResourceBundleStringMessagesImpl(STRING_MESSAGES_BASE_NAME, getClassLoader());
dataRetrieverChainDefinitions = new SailingDataRetrievalChainDefinitions();
predefinedQueries = new SailingPredefinedQueries();
}
@Override
public void start(BundleContext context) throws Exception {
INSTANCE = this;
this.context = context;
dataSourceProvidersHaveBeenInitialized = false;
super.start(context);
}
@Override
public void stop(BundleContext context) throws Exception {
this.context = null;
INSTANCE = null;
super.stop(context);
}
@Override
public ResourceBundleStringMessages getStringMessages() {
return sailingServerStringMessages;
}
@Override
public Iterable<Class<?>> getClassesWithMarkedMethods() {
Set<Class<?>> internalClasses = new HashSet<>();
internalClasses.add(HasTrackedRaceContext.class);
internalClasses.add(HasRaceResultOfCompetitorContext.class);
internalClasses.add(HasTrackedLegContext.class);
internalClasses.add(HasTrackedLegOfCompetitorContext.class);
internalClasses.add(HasGPSFixContext.class);
internalClasses.add(HasMarkPassingContext.class);
internalClasses.add(HasRaceOfCompetitorContext.class);
internalClasses.add(HasLeaderboardGroupContext.class);
internalClasses.add(HasLeaderboardContext.class);
return internalClasses;
}
@Override
public Iterable<DataRetrieverChainDefinition<?, ?>> getDataRetrieverChainDefinitions() {
return dataRetrieverChainDefinitions.get();
}
@Override
public Iterable<DataSourceProvider<?>> getDataSourceProviders() {
if (!dataSourceProvidersHaveBeenInitialized) {
initializeDataSourceProviders();
dataSourceProvidersHaveBeenInitialized = true;
}
return dataSourceProviders;
}
@Override
public Iterable<AggregationProcessorDefinition<?, ?>> getAggregationProcessorDefinitions() {
HashSet<AggregationProcessorDefinition<?, ?>> aggregators = new HashSet<>();
aggregators.add(ParallelBoolSumAggregationProcessor.getDefinition());
aggregators.add(ParallelDistanceSumAggregationProcessor.getDefinition());
aggregators.add(ParallelDistanceAverageAggregationProcessor.getDefinition());
aggregators.add(ParallelDistanceMaxAggregationProcessor.getDefinition());
aggregators.add(ParallelDistanceMinAggregationProcessor.getDefinition());
aggregators.add(ParallelDistanceMedianAggregationProcessor.getDefinition());
return aggregators;
}
@Override
public Map<PredefinedQueryIdentifier, StatisticQueryDefinitionDTO> getPredefinedQueries() {
return predefinedQueries.get();
}
private void initializeDataSourceProviders() {
dataSourceProviders = new HashSet<>();
dataSourceProviders.add(new RacingEventServiceProvider(context));
}
public static ClusterGroup<Speed> getWindStrengthInBeaufortClusterGroup() {
return clusterGroups.getWindStrengthInBeaufortCluster();
}
public static Activator getDefault() {
if (INSTANCE == null) {
INSTANCE = new Activator(); // probably non-OSGi case, as in test execution
}
return INSTANCE;
}
}
package com.sap.sailing.datamining;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.osgi.framework.BundleContext;
import com.sap.sailing.datamining.data.HasGPSFixContext;
import com.sap.sailing.datamining.data.HasLeaderboardContext;
import com.sap.sailing.datamining.data.HasLeaderboardGroupContext;
import com.sap.sailing.datamining.data.HasMarkPassingContext;
import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext;
import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelBoolSumAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceAverageAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMaxAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMedianAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMinAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceSumAggregationProcessor;
import com.sap.sailing.domain.common.Speed;
import com.sap.sse.datamining.DataSourceProvider;
import com.sap.sse.datamining.components.AggregationProcessorDefinition;
import com.sap.sse.datamining.components.DataRetrieverChainDefinition;
import com.sap.sse.datamining.data.ClusterGroup;
import com.sap.sse.datamining.impl.AbstractDataMiningActivatorWithPredefinedQueries;
import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO;
import com.sap.sse.datamining.shared.impl.PredefinedQueryIdentifier;
import com.sap.sse.i18n.ResourceBundleStringMessages;
import com.sap.sse.i18n.impl.ResourceBundleStringMessagesImpl;
public class Activator extends AbstractDataMiningActivatorWithPredefinedQueries {
private static final String STRING_MESSAGES_BASE_NAME = "stringmessages/Sailing_StringMessages";
private static final SailingClusterGroups clusterGroups = new SailingClusterGroups();
private static Activator INSTANCE;
private BundleContext context = null;
private final ResourceBundleStringMessages sailingServerStringMessages;
private final SailingDataRetrievalChainDefinitions dataRetrieverChainDefinitions;
private final SailingPredefinedQueries predefinedQueries;
private Collection<DataSourceProvider<?>> dataSourceProviders;
private boolean dataSourceProvidersHaveBeenInitialized;
public Activator() {
sailingServerStringMessages = new ResourceBundleStringMessagesImpl(STRING_MESSAGES_BASE_NAME, getClassLoader());
dataRetrieverChainDefinitions = new SailingDataRetrievalChainDefinitions();
predefinedQueries = new SailingPredefinedQueries();
}
@Override
public void start(BundleContext context) throws Exception {
INSTANCE = this;
this.context = context;
dataSourceProvidersHaveBeenInitialized = false;
super.start(context);
}
@Override
public void stop(BundleContext context) throws Exception {
this.context = null;
INSTANCE = null;
super.stop(context);
}
@Override
public ResourceBundleStringMessages getStringMessages() {
return sailingServerStringMessages;
}
@Override
public Iterable<Class<?>> getClassesWithMarkedMethods() {
Set<Class<?>> internalClasses = new HashSet<>();
internalClasses.add(HasTrackedRaceContext.class);
internalClasses.add(HasRaceResultOfCompetitorContext.class);
internalClasses.add(HasTrackedLegContext.class);
internalClasses.add(HasTrackedLegOfCompetitorContext.class);
internalClasses.add(HasGPSFixContext.class);
internalClasses.add(HasMarkPassingContext.class);
internalClasses.add(HasRaceOfCompetitorContext.class);
internalClasses.add(HasLeaderboardGroupContext.class);
internalClasses.add(HasLeaderboardContext.class);
return internalClasses;
}
@Override
public Iterable<DataRetrieverChainDefinition<?, ?>> getDataRetrieverChainDefinitions() {
return dataRetrieverChainDefinitions.get();
}
@Override
public Iterable<DataSourceProvider<?>> getDataSourceProviders() {
if (!dataSourceProvidersHaveBeenInitialized) {
initializeDataSourceProviders();
dataSourceProvidersHaveBeenInitialized = true;
}
return dataSourceProviders;
}
@Override
public Iterable<AggregationProcessorDefinition<?, ?>> getAggregationProcessorDefinitions() {
HashSet<AggregationProcessorDefinition<?, ?>> aggregators = new HashSet<>();
aggregators.add(ParallelBoolSumAggregationProcessor.getDefinition());
aggregators.add(ParallelDistanceSumAggregationProcessor.getDefinition());
aggregators.add(ParallelDistanceAverageAggregationProcessor.getDefinition());
aggregators.add(ParallelDistanceMaxAggregationProcessor.getDefinition());
aggregators.add(ParallelDistanceMinAggregationProcessor.getDefinition());
aggregators.add(ParallelDistanceMedianAggregationProcessor.getDefinition());
return aggregators;
}
@Override
public Map<PredefinedQueryIdentifier, StatisticQueryDefinitionDTO> getPredefinedQueries() {
return predefinedQueries.getQueries();
}
private void initializeDataSourceProviders() {
dataSourceProviders = new HashSet<>();
dataSourceProviders.add(new RacingEventServiceProvider(context));
}
public static ClusterGroup<Speed> getWindStrengthInBeaufortClusterGroup() {
return clusterGroups.getWindStrengthInBeaufortCluster();
}
public static Activator getDefault() {
if (INSTANCE == null) {
INSTANCE = new Activator(); // probably non-OSGi case, as in test execution
}
return INSTANCE;
}
}
@@ -1,143 +1,194 @@
package com.sap.sailing.datamining;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.sap.sailing.datamining.data.HasGPSFixContext;
import com.sap.sailing.datamining.data.HasLeaderboardContext;
import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.datamining.impl.components.GPSFixRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.LeaderboardGroupRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.LeaderboardRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.RaceOfCompetitorRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedLegRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedRaceRetrievalProcessor;
import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO;
import com.sap.sse.datamining.shared.impl.PredefinedQueryIdentifier;
import com.sap.sse.datamining.shared.impl.dto.AggregationProcessorDefinitionDTO;
import com.sap.sse.datamining.shared.impl.dto.DataRetrieverChainDefinitionDTO;
import com.sap.sse.datamining.shared.impl.dto.DataRetrieverLevelDTO;
import com.sap.sse.datamining.shared.impl.dto.FunctionDTO;
import com.sap.sse.datamining.shared.impl.dto.LocalizedTypeDTO;
import com.sap.sse.datamining.shared.impl.dto.ModifiableStatisticQueryDefinitionDTO;
public class SailingPredefinedQueries {
private final Map<PredefinedQueryIdentifier, StatisticQueryDefinitionDTO> predefinedQueries;
public SailingPredefinedQueries() {
predefinedQueries = new HashMap<>();
predefinedQueries.put(new PredefinedQueryIdentifier("AvgSpeed_Per_Regatta-Race", "Average Speed grouped by Regatta Name and Race Name"),
create_AvgSpeed_Per_Regatta_Race());
predefinedQueries.put(new PredefinedQueryIdentifier("AvgSpeed_Per_Competitor-LegType", "Average Speed grouped by Competitor Team Name and Leg Type"),
create_AvgSpeed_Per_Competitor_LegType());
predefinedQueries.put(new PredefinedQueryIdentifier("SumDistance_Per_Competitor-LegType", "Sum of the traveled Distance grouped by Competitor Team Name and Leg Type"),
create_SumDistance_Per_Competitor_LegType());
predefinedQueries.put(new PredefinedQueryIdentifier("SumManeuvers_Per_Competitor", "Sum of the performed Maneuvers grouped by Competitor Team Name"),
create_SumManeuvers_Per_Competitor());
}
private StatisticQueryDefinitionDTO create_AvgSpeed_Per_Regatta_Race() {
FunctionDTO statistic = new FunctionDTO(false, "getGPSFix().getSpeed().getKnots()", HasGPSFixContext.class.getName(), double.class.getName(), new ArrayList<String>(), "", 0);
AggregationProcessorDefinitionDTO aggregator = new AggregationProcessorDefinitionDTO("Average", Number.class.getName(), Number.class.getName(), "");
ArrayList<DataRetrieverLevelDTO> retrieverLevels = new ArrayList<>();
retrieverLevels.add(new DataRetrieverLevelDTO(0, LeaderboardGroupRetrievalProcessor.class.getName(), new LocalizedTypeDTO(LeaderboardGroupWithContext.class.getName(), "Leaderboard Group"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(1, LeaderboardRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasLeaderboardContext.class.getName(), "Leaderboard"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(2, TrackedRaceRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedRaceContext.class.getName(), "Race"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(3, TrackedLegRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegContext.class.getName(), "Leg"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(4, TrackedLegOfCompetitorRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegOfCompetitorContext.class.getName(), "Leg of competitor"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(5, GPSFixRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasGPSFixContext.class.getName(), "GPS-Fix"), null));
DataRetrieverChainDefinitionDTO retrieverChain = new DataRetrieverChainDefinitionDTO("", RacingEventService.class.getName(), retrieverLevels);
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getTrackedLegContext().getTrackedRaceContext().getRegatta().getName()", HasGPSFixContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
FunctionDTO dimensionToGroupBy1 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getTrackedLegContext().getTrackedRaceContext().getRace().getName()", HasGPSFixContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy1);
return queryDefinition;
}
private StatisticQueryDefinitionDTO create_AvgSpeed_Per_Competitor_LegType() {
FunctionDTO statistic = new FunctionDTO(false, "getGPSFix().getSpeed().getKnots()", HasGPSFixContext.class.getName(), double.class.getName(), new ArrayList<String>(), "", 0);
AggregationProcessorDefinitionDTO aggregator = new AggregationProcessorDefinitionDTO("Average", Number.class.getName(), Number.class.getName(), "");
ArrayList<DataRetrieverLevelDTO> retrieverLevels = new ArrayList<>();
retrieverLevels.add(new DataRetrieverLevelDTO(0, LeaderboardGroupRetrievalProcessor.class.getName(), new LocalizedTypeDTO(LeaderboardGroupWithContext.class.getName(), "Leaderboard Group"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(1, LeaderboardRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasLeaderboardContext.class.getName(), "Leaderboard"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(2, TrackedRaceRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedRaceContext.class.getName(), "Race"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(3, TrackedLegRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegContext.class.getName(), "Leg"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(4, TrackedLegOfCompetitorRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegOfCompetitorContext.class.getName(), "Leg of competitor"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(5, GPSFixRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasGPSFixContext.class.getName(), "GPS-Fix"), null));
DataRetrieverChainDefinitionDTO retrieverChain = new DataRetrieverChainDefinitionDTO("", RacingEventService.class.getName(), retrieverLevels);
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getCompetitor().getTeam().getName()", HasGPSFixContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
FunctionDTO dimensionToGroupBy1 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getTrackedLegContext().getLegType()", HasGPSFixContext.class.getName(), LegType.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy1);
return queryDefinition;
}
private StatisticQueryDefinitionDTO create_SumDistance_Per_Competitor_LegType() {
FunctionDTO statistic = new FunctionDTO(false, "getDistanceTraveled()", HasTrackedLegOfCompetitorContext.class.getName(), Distance.class.getName(), new ArrayList<String>(), "", 0);
AggregationProcessorDefinitionDTO aggregator = new AggregationProcessorDefinitionDTO("Sum", Distance.class.getName(), Distance.class.getName(), "");
ArrayList<DataRetrieverLevelDTO> retrieverLevels = new ArrayList<>();
retrieverLevels.add(new DataRetrieverLevelDTO(0, LeaderboardGroupRetrievalProcessor.class.getName(), new LocalizedTypeDTO(LeaderboardGroupWithContext.class.getName(), "Leaderboard Group"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(1, LeaderboardRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasLeaderboardContext.class.getName(), "Leaderboard"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(2, TrackedRaceRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedRaceContext.class.getName(), "Race"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(3, TrackedLegRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegContext.class.getName(), "Leg"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(4, TrackedLegOfCompetitorRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegOfCompetitorContext.class.getName(), "Leg of competitor"), null));
DataRetrieverChainDefinitionDTO retrieverChain = new DataRetrieverChainDefinitionDTO("", RacingEventService.class.getName(), retrieverLevels);
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getCompetitor().getTeam().getName()", HasTrackedLegOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
FunctionDTO dimensionToGroupBy1 = new FunctionDTO(true, "getTrackedLegContext().getLegType()", HasTrackedLegOfCompetitorContext.class.getName(), LegType.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy1);
return queryDefinition;
}
private StatisticQueryDefinitionDTO create_SumManeuvers_Per_Competitor() {
FunctionDTO statistic = new FunctionDTO(false, "getNumberOfManeuvers()", HasRaceOfCompetitorContext.class.getName(), int.class.getName(), new ArrayList<String>(), "", 0);
AggregationProcessorDefinitionDTO aggregator = new AggregationProcessorDefinitionDTO("Sum", Number.class.getName(), Number.class.getName(), "");
ArrayList<DataRetrieverLevelDTO> retrieverLevels = new ArrayList<>();
retrieverLevels.add(new DataRetrieverLevelDTO(0, LeaderboardGroupRetrievalProcessor.class.getName(), new LocalizedTypeDTO(LeaderboardGroupWithContext.class.getName(), "Leaderboard Group"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(1, LeaderboardRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasLeaderboardContext.class.getName(), "Leaderboard"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(2, TrackedRaceRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedRaceContext.class.getName(), "Race"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(3, RaceOfCompetitorRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasRaceOfCompetitorContext.class.getName(), "Competitor"), null));
DataRetrieverChainDefinitionDTO retrieverChain = new DataRetrieverChainDefinitionDTO("", RacingEventService.class.getName(), retrieverLevels);
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getCompetitor().getTeam().getName()", HasRaceOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
return queryDefinition;
}
public Map<PredefinedQueryIdentifier, StatisticQueryDefinitionDTO> get() {
return predefinedQueries;
}
}
package com.sap.sailing.datamining;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.sap.sailing.datamining.data.HasGPSFixContext;
import com.sap.sailing.datamining.data.HasLeaderboardContext;
import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.datamining.impl.components.GPSFixRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.LeaderboardGroupRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.LeaderboardRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.RaceOfCompetitorRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedLegRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedRaceRetrievalProcessor;
import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO;
import com.sap.sse.datamining.shared.impl.PredefinedQueryIdentifier;
import com.sap.sse.datamining.shared.impl.dto.AggregationProcessorDefinitionDTO;
import com.sap.sse.datamining.shared.impl.dto.DataRetrieverChainDefinitionDTO;
import com.sap.sse.datamining.shared.impl.dto.DataRetrieverLevelDTO;
import com.sap.sse.datamining.shared.impl.dto.FunctionDTO;
import com.sap.sse.datamining.shared.impl.dto.LocalizedTypeDTO;
import com.sap.sse.datamining.shared.impl.dto.ModifiableStatisticQueryDefinitionDTO;
public class SailingPredefinedQueries {
private final Map<PredefinedQueryIdentifier, StatisticQueryDefinitionDTO> predefinedQueries;
public final static String QUERY_AVERAGE_SPEED_PER_REGATTA_RACE = "AvgSpeed_Per_Regatta-Race";
public final static String QUERY_AVERAGE_SPEED_PER_COMPETITOR_LEGTYPE = "AvgSpeed_Per_Competitor-LegType";
public final static String QUERY_AVERAGE_SPEED_PER_COMPETITOR = "AvgSpeed_Per_Competitor";
public final static String QUERY_DISTANCE_TRAVELED_PER_COMPETITOR_LEGTYPE = "DistanceTraveled_Per_Competitor-LegType";
public final static String QUERY_DISTANCE_TRAVELED_PER_COMPETITOR = "DistanceTraveled_Per_Competitor";
public final static String QUERY_MANEUVERS_PER_COMPETITOR = "Maneuvers_Per_Competitor";
public SailingPredefinedQueries() {
predefinedQueries = new HashMap<>();
predefinedQueries.put(new PredefinedQueryIdentifier(QUERY_AVERAGE_SPEED_PER_REGATTA_RACE, "Average Speed grouped by regatta name and race name"),
create_AvgSpeed_Per_Regatta_Race());
predefinedQueries.put(new PredefinedQueryIdentifier(QUERY_AVERAGE_SPEED_PER_COMPETITOR_LEGTYPE, "Average speed grouped by competitor/team name and leg type"),
create_AvgSpeed_Per_Competitor_LegType());
predefinedQueries.put(new PredefinedQueryIdentifier(QUERY_AVERAGE_SPEED_PER_COMPETITOR, "Average speed grouped by competitor/team name"),
create_AvgSpeed_Per_Competitor());
predefinedQueries.put(new PredefinedQueryIdentifier(QUERY_DISTANCE_TRAVELED_PER_COMPETITOR_LEGTYPE, "Distance traveled grouped by competitor/team name and leg type"),
create_SumTraveledDistance_Per_Competitor_LegType());
predefinedQueries.put(new PredefinedQueryIdentifier(QUERY_DISTANCE_TRAVELED_PER_COMPETITOR, "Distance traveled grouped by competitor/team name and leg type"),
create_SumTraveledDistance_Per_Competitor());
predefinedQueries.put(new PredefinedQueryIdentifier(QUERY_MANEUVERS_PER_COMPETITOR, "Maneuvers grouped by competitor/team name"),
create_SumManeuvers_Per_Competitor());
}
private StatisticQueryDefinitionDTO create_AvgSpeed_Per_Regatta_Race() {
FunctionDTO statistic = new FunctionDTO(false, "getGPSFix().getSpeed().getKnots()", HasGPSFixContext.class.getName(), double.class.getName(), new ArrayList<String>(), "", 0);
AggregationProcessorDefinitionDTO aggregator = new AggregationProcessorDefinitionDTO("Average", Number.class.getName(), Number.class.getName(), "");
ArrayList<DataRetrieverLevelDTO> retrieverLevels = new ArrayList<>();
retrieverLevels.add(new DataRetrieverLevelDTO(0, LeaderboardGroupRetrievalProcessor.class.getName(), new LocalizedTypeDTO(LeaderboardGroupWithContext.class.getName(), "Leaderboard Group"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(1, LeaderboardRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasLeaderboardContext.class.getName(), "Leaderboard"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(2, TrackedRaceRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedRaceContext.class.getName(), "Race"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(3, TrackedLegRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegContext.class.getName(), "Leg"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(4, TrackedLegOfCompetitorRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegOfCompetitorContext.class.getName(), "Leg of competitor"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(5, GPSFixRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasGPSFixContext.class.getName(), "GPS-Fix"), null));
DataRetrieverChainDefinitionDTO retrieverChain = new DataRetrieverChainDefinitionDTO("", RacingEventService.class.getName(), retrieverLevels);
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getTrackedLegContext().getTrackedRaceContext().getRegatta().getName()", HasGPSFixContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
FunctionDTO dimensionToGroupBy1 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getTrackedLegContext().getTrackedRaceContext().getRace().getName()", HasGPSFixContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy1);
return queryDefinition;
}
private StatisticQueryDefinitionDTO create_AvgSpeed_Per_Competitor_LegType() {
FunctionDTO statistic = new FunctionDTO(false, "getGPSFix().getSpeed().getKnots()", HasGPSFixContext.class.getName(), double.class.getName(), new ArrayList<String>(), "", 0);
AggregationProcessorDefinitionDTO aggregator = new AggregationProcessorDefinitionDTO("Average", Number.class.getName(), Number.class.getName(), "");
ArrayList<DataRetrieverLevelDTO> retrieverLevels = new ArrayList<>();
retrieverLevels.add(new DataRetrieverLevelDTO(0, LeaderboardGroupRetrievalProcessor.class.getName(), new LocalizedTypeDTO(LeaderboardGroupWithContext.class.getName(), "Leaderboard Group"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(1, LeaderboardRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasLeaderboardContext.class.getName(), "Leaderboard"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(2, TrackedRaceRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedRaceContext.class.getName(), "Race"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(3, TrackedLegRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegContext.class.getName(), "Leg"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(4, TrackedLegOfCompetitorRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegOfCompetitorContext.class.getName(), "Leg of competitor"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(5, GPSFixRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasGPSFixContext.class.getName(), "GPS-Fix"), null));
DataRetrieverChainDefinitionDTO retrieverChain = new DataRetrieverChainDefinitionDTO("", RacingEventService.class.getName(), retrieverLevels);
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getCompetitor().getTeam().getName()", HasGPSFixContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
FunctionDTO dimensionToGroupBy1 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getTrackedLegContext().getLegType()", HasGPSFixContext.class.getName(), LegType.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy1);
return queryDefinition;
}
private StatisticQueryDefinitionDTO create_SumTraveledDistance_Per_Competitor_LegType() {
FunctionDTO statistic = new FunctionDTO(false, "getDistanceTraveled()", HasTrackedLegOfCompetitorContext.class.getName(), Distance.class.getName(), new ArrayList<String>(), "", 0);
AggregationProcessorDefinitionDTO aggregator = new AggregationProcessorDefinitionDTO("Sum", Distance.class.getName(), Distance.class.getName(), "");
ArrayList<DataRetrieverLevelDTO> retrieverLevels = new ArrayList<>();
retrieverLevels.add(new DataRetrieverLevelDTO(0, LeaderboardGroupRetrievalProcessor.class.getName(), new LocalizedTypeDTO(LeaderboardGroupWithContext.class.getName(), "Leaderboard Group"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(1, LeaderboardRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasLeaderboardContext.class.getName(), "Leaderboard"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(2, TrackedRaceRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedRaceContext.class.getName(), "Race"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(3, TrackedLegRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegContext.class.getName(), "Leg"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(4, TrackedLegOfCompetitorRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegOfCompetitorContext.class.getName(), "Leg of competitor"), null));
DataRetrieverChainDefinitionDTO retrieverChain = new DataRetrieverChainDefinitionDTO("", RacingEventService.class.getName(), retrieverLevels);
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getCompetitor().getTeam().getName()", HasTrackedLegOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
FunctionDTO dimensionToGroupBy1 = new FunctionDTO(true, "getTrackedLegContext().getLegType()", HasTrackedLegOfCompetitorContext.class.getName(), LegType.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy1);
return queryDefinition;
}
private StatisticQueryDefinitionDTO create_AvgSpeed_Per_Competitor() {
FunctionDTO statistic = new FunctionDTO(false, "getGPSFix().getSpeed().getKnots()", HasGPSFixContext.class.getName(), double.class.getName(), new ArrayList<String>(), "", 0);
AggregationProcessorDefinitionDTO aggregator = new AggregationProcessorDefinitionDTO("Average", Number.class.getName(), Number.class.getName(), "");
ArrayList<DataRetrieverLevelDTO> retrieverLevels = new ArrayList<>();
retrieverLevels.add(new DataRetrieverLevelDTO(0, LeaderboardGroupRetrievalProcessor.class.getName(), new LocalizedTypeDTO(LeaderboardGroupWithContext.class.getName(), "Leaderboard Group"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(1, LeaderboardRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasLeaderboardContext.class.getName(), "Leaderboard"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(2, TrackedRaceRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedRaceContext.class.getName(), "Race"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(3, TrackedLegRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegContext.class.getName(), "Leg"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(4, TrackedLegOfCompetitorRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegOfCompetitorContext.class.getName(), "Leg of competitor"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(5, GPSFixRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasGPSFixContext.class.getName(), "GPS-Fix"), null));
DataRetrieverChainDefinitionDTO retrieverChain = new DataRetrieverChainDefinitionDTO("", RacingEventService.class.getName(), retrieverLevels);
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getCompetitor().getTeam().getName()", HasGPSFixContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
return queryDefinition;
}
private StatisticQueryDefinitionDTO create_SumTraveledDistance_Per_Competitor() {
FunctionDTO statistic = new FunctionDTO(false, "getDistanceTraveled()", HasTrackedLegOfCompetitorContext.class.getName(), Distance.class.getName(), new ArrayList<String>(), "", 0);
AggregationProcessorDefinitionDTO aggregator = new AggregationProcessorDefinitionDTO("Sum", Distance.class.getName(), Distance.class.getName(), "");
ArrayList<DataRetrieverLevelDTO> retrieverLevels = new ArrayList<>();
retrieverLevels.add(new DataRetrieverLevelDTO(0, LeaderboardGroupRetrievalProcessor.class.getName(), new LocalizedTypeDTO(LeaderboardGroupWithContext.class.getName(), "Leaderboard Group"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(1, LeaderboardRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasLeaderboardContext.class.getName(), "Leaderboard"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(2, TrackedRaceRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedRaceContext.class.getName(), "Race"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(3, TrackedLegRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegContext.class.getName(), "Leg"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(4, TrackedLegOfCompetitorRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedLegOfCompetitorContext.class.getName(), "Leg of competitor"), null));
DataRetrieverChainDefinitionDTO retrieverChain = new DataRetrieverChainDefinitionDTO("", RacingEventService.class.getName(), retrieverLevels);
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getCompetitor().getTeam().getName()", HasTrackedLegOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
return queryDefinition;
}
private StatisticQueryDefinitionDTO create_SumManeuvers_Per_Competitor() {
FunctionDTO statistic = new FunctionDTO(false, "getNumberOfManeuvers()", HasRaceOfCompetitorContext.class.getName(), int.class.getName(), new ArrayList<String>(), "", 0);
AggregationProcessorDefinitionDTO aggregator = new AggregationProcessorDefinitionDTO("Sum", Number.class.getName(), Number.class.getName(), "");
ArrayList<DataRetrieverLevelDTO> retrieverLevels = new ArrayList<>();
retrieverLevels.add(new DataRetrieverLevelDTO(0, LeaderboardGroupRetrievalProcessor.class.getName(), new LocalizedTypeDTO(LeaderboardGroupWithContext.class.getName(), "Leaderboard Group"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(1, LeaderboardRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasLeaderboardContext.class.getName(), "Leaderboard"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(2, TrackedRaceRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasTrackedRaceContext.class.getName(), "Race"), null));
retrieverLevels.add(new DataRetrieverLevelDTO(3, RaceOfCompetitorRetrievalProcessor.class.getName(), new LocalizedTypeDTO(HasRaceOfCompetitorContext.class.getName(), "Competitor"), null));
DataRetrieverChainDefinitionDTO retrieverChain = new DataRetrieverChainDefinitionDTO("", RacingEventService.class.getName(), retrieverLevels);
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getCompetitor().getTeam().getName()", HasRaceOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
return queryDefinition;
}
public Map<PredefinedQueryIdentifier, StatisticQueryDefinitionDTO> getQueries() {
return predefinedQueries;
}
}
@@ -11,6 +11,7 @@ Export-Package: com.sap.sailing.domain.common,
com.sap.sailing.domain.common.confidence,
com.sap.sailing.domain.common.confidence.impl,
com.sap.sailing.domain.common.dto,
com.sap.sailing.domain.common.i18n,
com.sap.sailing.domain.common.impl,
com.sap.sailing.domain.common.media,
com.sap.sailing.domain.common.quadtree,
@@ -16,7 +16,12 @@ public abstract class AbstractSpeedImpl implements Speed {
@Override
public Distance travel(TimePoint t1, TimePoint t2) {
return new NauticalMileDistance((t2.asMillis() - t1.asMillis()) / 1000. / 3600. * getKnots());
return travel(t1.until(t2));
}
@Override
public Distance travel(Duration duration) {
return new NauticalMileDistance(duration.asHours() * getKnots());
}
@Override
@@ -26,6 +26,7 @@ public enum BoatClassMasterdata {
CANOE_IC ("International Canoe", true, 5.20, 1.01, BoatHullType.MONOHULL, true, "Canoe IC", "Canoe-IC", "IC", "Kanu IC", "International Canoe", "International 10 Sq.m. Sailing Canoe"),
CANOE_TAIFUN ("Canoe Taifun", true, 5.20, 1.32, BoatHullType.MONOHULL, false, "Taifun", "Taifun Kanu", "Kanu Taifun"),
CONTENDER ("Contender", true, 4.88, 1.42, BoatHullType.MONOHULL, false),
CC_30 ("C&C 30", true, 9.12, 3.25, BoatHullType.MONOHULL, true),
D_ONE ("D-One", true, 4.23, 2.31, BoatHullType.MONOHULL, true, "Devoti D-One", "DOne", "D_One"),
DRAGON_INT ("Dragon Int.", true, 8.89, 1.96, BoatHullType.MONOHULL, true, "Drachen", "Dragon"),
DYAS("Dyas", true, 7.15, 1.95, BoatHullType.MONOHULL, true),
@@ -35,13 +36,15 @@ public enum BoatClassMasterdata {
EUROPE_INT ("Europe Int.", true, 3.35, 1.35, BoatHullType.MONOHULL, false, "Europe"),
F_18 ("Formula 18", true, 6.85, 2.25, BoatHullType.CATAMARAN, true, "F18", "F-18"),
FARR_30 ("Farr 30", true, 9.42, 3.08, BoatHullType.MONOHULL, true, "F30", "F-30", "Farr-30"),
FARR_280 ("Farr 280", true, 8.72, 2.87, BoatHullType.MONOHULL, true, "F280", "F-280", "Farr-280"),
FINN ("Finn", true, 4.50, 1.51, BoatHullType.MONOHULL, false),
FLYING_DUTCHMAN ("Flying Dutchman", true, 6.10, 1.80, BoatHullType.MONOHULL, true),
FLYING_DUTCHMAN ("Flying Dutchman", true, 6.10, 1.80, BoatHullType.MONOHULL, true, "FD"),
FLYING_PHANTOM ("Flying Phantom", true, 5.52, 3.00, BoatHullType.CATAMARAN, true),
FOLKBOAT ("Folkboat", true, 7.68, 2.20, BoatHullType.MONOHULL, false, "Folke", "Folkeboot"),
FUN ("FUN", true, 7.20, 2.45, BoatHullType.MONOHULL, true, "FUN O.D.", "FUN OD", "Open FUN"),
F_16 ("Formula 16", true, 5.00, 2.50, BoatHullType.CATAMARAN, true, "F16", "F-16"),
GC_32 ("GC 32", false, 10.0, 6.00, BoatHullType.CATAMARAN, true, "GC32", "GC-32"),
GP_26 ("GP 26", true, 7.90, 2.55, BoatHullType.MONOHULL, true, "GP26", "GP-26"),
HOBIE_16 ("Hobie 16", true, 5.05, 2.41, BoatHullType.CATAMARAN, false, "H16"),
H_BOAT ("H-Boat", true, 8.28, 2.18, BoatHullType.MONOHULL, true, "HB"),
HOBIE_TIGER ("Hobie Tiger", true, 5.51, 2.60, BoatHullType.CATAMARAN, true),
@@ -51,6 +54,9 @@ public enum BoatClassMasterdata {
J24 ("J/24", true, 7.32, 2.67, BoatHullType.MONOHULL, true, "J24", "J-24"),
J70 ("J/70", true, 6.93, 2.25, BoatHullType.MONOHULL, true, "J70", "J-70"),
J80 ("J/80", true, 8.0, 2.51, BoatHullType.MONOHULL, true, "J80", "J-80"),
J88 ("J/88", true, 8.9, 1.89, BoatHullType.MONOHULL, true, "J88", "J-88"),
J105 ("J/105", true, 10.52, 3.35, BoatHullType.MONOHULL, true, "J105", "J-105"),
J111 ("J/111", true, 11.1, 3.29, BoatHullType.MONOHULL, true, "J111", "J-111"),
KIELZUGVOGEL ("Kielzugvogel", true, 5.80, 1.88, BoatHullType.MONOHULL, false, "KZV"),
KITE ("Kite", true, 3.35, 1.52, BoatHullType.MONOHULL, false),
LASER_2 ("Laser 2", true, 4.39, 1.42, BoatHullType.MONOHULL, false, "Laser II", "Laser2", "Laser-2", "Laser-II"),
@@ -59,6 +65,7 @@ public enum BoatClassMasterdata {
LASER_INT ("Laser Int.", true, 4.19, 1.39, BoatHullType.MONOHULL, false, "Laser", "LSR"),
LASER_SB3 ("Laser SB3", true, 6.15, 2.15, BoatHullType.MONOHULL, false, "LSB3", "SB20"),
LAGO_26 ("Lago 26", true, 7.95, 2.50, BoatHullType.MONOHULL, true, "Lago26"),
MELGES_20 ("Melges 20", true, 6.10, 2.13, BoatHullType.MONOHULL, true, "Melges-20", "M20"),
MELGES_24 ("Melges 24", true, 7.32, 2.50, BoatHullType.MONOHULL, true, "Melges-24", "M24"),
MINI_TRANSAT ("Mini Transat 6.50", true, 6.50, 3.00, BoatHullType.MONOHULL, true, "Mini Transat"),
MUSTO_SKIFF ("Musto Skiff", true, 4.55, 1.35, BoatHullType.MONOHULL, true, "Musto Performance Skiff", "MPS", "Musto"),
@@ -78,14 +85,20 @@ public enum BoatClassMasterdata {
STAR ("Star", true, 6.92, 1.74, BoatHullType.MONOHULL, false, "STR"),
STREAMLINE ("Streamline", true, 7.15, 2.55, BoatHullType.MONOHULL, true),
SWAN_45 ("Swan 45", true, 13.83, 3.91, BoatHullType.MONOHULL, true, "Swan", "Swan-45"),
TARTAN_10 ("Tartan 10", true, 10.10, 2.82, BoatHullType.MONOHULL, true),
TORNADO ("Tornado Catamaran", true, 6.10, 3.02, BoatHullType.CATAMARAN, true, "Tornado", "Tornado Cat"),
TRIAS ("Trias", true, 9.20, 2.12, BoatHullType.MONOHULL, true),
VIPER_640 ("Viper 640", true, 6.43, 2.49, BoatHullType.MONOHULL, true),
VX_ONE ("VX ONE", true, 5.79, 2.19, BoatHullType.MONOHULL, true, "VX-ONE"),
X_99 ("X-99", true, 9.96, 2.95, BoatHullType.MONOHULL, true, "X99"),
// multi-class "boat classes"; to be replaced at some later point in time by something like a RegattaClass
ORC ("ORC", true, 13.83, 3.91, BoatHullType.MONOHULL, true),
ORC_CLUB ("ORC Club", true, 13.83, 3.91, BoatHullType.MONOHULL, true),
ORC_INTERNATIONAL ("ORC International", true, 13.83, 3.91, BoatHullType.MONOHULL, true, "ORC Int.");
ORC_INTERNATIONAL ("ORC International", true, 13.83, 3.91, BoatHullType.MONOHULL, true, "ORC Int."),
// Performance Handicap Racing Fleet (PHRF) is a handicap system used for racing in North America
PHRF ("PHRF", true, 13.83, 3.91, BoatHullType.MONOHULL, true);
private final String displayName;
private final String[] alternativeNames;
@@ -9,15 +9,23 @@ import java.util.List;
* When modifying these values also check res/preferences.xml of racecommittee.app!
*/
public enum CourseDesignerMode {
UNKNOWN("Unknown"),
BY_NAME("By-Name Course Designer"), // O2, O3, I2,...
BY_MAP("By-Map Course Designer"), // Map-based Course Designer
BY_MARKS("By-Marks Course Designer"); // ESS
UNKNOWN("Unknown", /* waypointSequenceValid */ false),
BY_NAME("By-Name Course Designer", /* waypointSequenceValid */ false), // O2, O3, I2,...
BY_MAP("By-Map Course Designer", /* waypointSequenceValid */ true), // Map-based Course Designer
BY_MARKS("By-Marks Course Designer", /* waypointSequenceValid */ true), // ESS
ADMIN_CONSOLE("Administration Console", /* waypointSequenceValid */ true);
private String displayName;
private final String displayName;
/**
* Some course designers may only be able to tell, say, a name for the new course but not
* necessarily a valid waypoint sequence. This flag tells.
*/
private final boolean waypointSequenceValid;
private CourseDesignerMode(String displayName) {
private CourseDesignerMode(String displayName, boolean waypointSequenceValid) {
this.displayName = displayName;
this.waypointSequenceValid = waypointSequenceValid;
}
@Override
@@ -25,6 +33,10 @@ public enum CourseDesignerMode {
return displayName;
}
public boolean isWaypointSequenceValid() {
return waypointSequenceValid;
}
public static CourseDesignerMode[] validValues() {
List<CourseDesignerMode> validValues = new ArrayList<CourseDesignerMode>();
for (CourseDesignerMode type : values()) {
@@ -4,14 +4,40 @@ import java.io.Serializable;
import java.util.UUID;
public interface DataImportProgress extends Serializable {
/**
* Helper for i18n of import sub progresses messages. A method according to the messageKey is required
* in com.sap.sailing.gwt.ui.masterdataimport.MasterDataImportSubProgressMessages interface.
*/
enum SubProgress {
CONNECTION_SETUP("connectionSetup"),
CONNECTION_ESTABLISH("connectionEstablish"),
TRANSFER_STARTED("transferStarted"),
TRANSFER_COMPLETED("transferCompleted"),
IMPORT_INIT("importInit"),
IMPORT_WAIT("importWait"),
IMPORT_LEADERBOARD_GROUPS("importLeaderboardGroups"),
IMPORT_WIND_TRACKS("importWindTracks"),
UPDATE_EVENT_LEADERBOARD_GROUP_LINKS("updateEventLeaderboardGroupLinks");
private final String messageKey;
private SubProgress(String messageKey) {
this.messageKey = messageKey;
}
public String getMessageKey() {
return messageKey;
}
}
double getOverallProgressPct();
void setOverAllProgressPct(double pct);
String getNameOfCurrentSubProgress();
void setNameOfCurrentSubProgress(String name);
DataImportSubProgress getCurrentSubProgress();
void setCurrentSubProgress(DataImportSubProgress subProgress);
double getCurrentSubProgressPct();
@@ -0,0 +1,70 @@
package com.sap.sailing.domain.common;
import com.sap.sailing.domain.common.i18n.CommonStringMessages;
public enum DataImportSubProgress {
INIT {
@Override
public String getMessage(CommonStringMessages messages) {
return messages.init();
}
},
CONNECTION_SETUP {
@Override
public String getMessage(CommonStringMessages messages) {
return messages.connectionSetup();
}
},
CONNECTION_ESTABLISH {
@Override
public String getMessage(CommonStringMessages messages) {
return messages.connectionEstablish();
}
},
TRANSFER_STARTED {
@Override
public String getMessage(CommonStringMessages messages) {
return messages.transferStarted();
}
},
TRANSFER_COMPLETED {
@Override
public String getMessage(CommonStringMessages messages) {
return messages.transferCompleted();
}
},
IMPORT_INIT {
@Override
public String getMessage(CommonStringMessages messages) {
return messages.importInit();
}
},
IMPORT_WAIT {
@Override
public String getMessage(CommonStringMessages messages) {
return messages.importWait();
}
},
IMPORT_LEADERBOARD_GROUPS {
@Override
public String getMessage(CommonStringMessages messages) {
return messages.importLeaderboardGroups();
}
},
IMPORT_WIND_TRACKS {
@Override
public String getMessage(CommonStringMessages messages) {
return messages.importWindTracks();
}
},
UPDATE_EVENT_LEADERBOARD_GROUP_LINKS {
@Override
public String getMessage(CommonStringMessages messages) {
return messages.updateEventLeaderboardGroupLinks();
}
};
public abstract String getMessage(CommonStringMessages messages);
}
@@ -14,7 +14,8 @@ public enum FleetColors {
RED (232, 74, 26, 0),
PURPLE (212, 28, 175, 0),
NEON_GREEN (118, 255, 33, 0),
ORANGE (255, 174, 23, 0);
ORANGE (255, 174, 23, 0),
WHITE (250, 250, 250, 0);
private Color color;
@@ -42,7 +42,9 @@ public enum MaxPointsReason {
/** Retired */
RET(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true),
/** Uniform Flag Disqualification */
UFD(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true);
UFD(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true),
/** Time limit Expired */
TLE(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true);
private final boolean discardable;
@@ -14,20 +14,34 @@ package com.sap.sailing.domain.common;
public enum PassingInstruction {
None(1, 2),
Port(1),
Single_Unknown(1),
Starboard(1),
Single_Unknown(1),
Gate(2),
Line(2),
Offset(2),
FixedBearing(1);
/**
* Those values relevant for picking by a user when creating a waypoint; for example, this list
* does not include the {@link #None} or the {@link #Single_Unknown} literals. No guarantees
* made as to the ordering. The enum literals may change, be extended, have elements inserted or
* removed at any time!
*/
public static PassingInstruction[] relevantValues() {
PassingInstruction[] uiValues = new PassingInstruction[PassingInstruction.values().length - 1];
final PassingInstruction[] uselessValues = new PassingInstruction[] { None, Single_Unknown, Offset, FixedBearing };
PassingInstruction[] uiValues = new PassingInstruction[PassingInstruction.values().length - uselessValues.length];
int i = 0;
for (PassingInstruction p : PassingInstruction.values()) {
if (p != PassingInstruction.None)
boolean useless = false;
for (PassingInstruction u : uselessValues) {
if (u == p) {
useless = true;
break;
}
}
if (!useless) {
uiValues[i++] = p;
}
}
return uiValues;
}
@@ -1,4 +1,4 @@
package com.sap.sailing.simulator;
package com.sap.sailing.domain.common;
public enum PathType {
OMNISCIENT("1#Omniscient"),
@@ -60,6 +60,11 @@ public interface Speed extends Comparable<Speed>, Serializable {
public double getStatuteMilesPerHour() {
return 0;
}
@Override
public Distance travel(Duration duration) {
return Distance.NULL;
}
};
@Statistic(messageKey="", resultDecimals=2)
@@ -85,4 +90,6 @@ public interface Speed extends Comparable<Speed>, Serializable {
* line" on a sphere) with this speed.
*/
Duration getDuration(Distance distance);
Distance travel(Duration duration);
}
@@ -0,0 +1,25 @@
package com.sap.sailing.domain.common.abstractlog;
import java.io.Serializable;
import com.sap.sse.common.TimePoint;
/**
* Events from {@link RaceLog}s and {@link RegattaLog}s may be used to specify timing aspects for a race.
* When searching for such events, three types of outcome are possible: a valid time point specification
* may be found; or an explicit specification of {@code null} may be found (such as forcing a tracking
* interval to be open at one end); or no specification is found.<p>
*
* If only a {@link TimePoint} were used, the last two cases could not be disambiguated when specifying
* {@code null} as this {@link TimePoint}. This interface makes the distinction possible. If the reference
* to an object of this type is {@code null} it means that no information regarding the time point was found
* in the log. If a valid {@link TimePointSpecificationFoundInLog} object is presented, information was found in the log,
* but the {@link #getTimePoint} could still be {@code null}, meaning that a log entry explicitly set this
* time point to {@code null}.
*
* @author Axel Uhl (d043530)
*
*/
public interface TimePointSpecificationFoundInLog extends Serializable {
TimePoint getTimePoint();
}
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.common.abstractlog;
import com.sap.sse.common.TimePoint;
public class TimePointSpecificationFoundInLogImpl implements TimePointSpecificationFoundInLog {
private static final long serialVersionUID = -5499989656571658254L;
private TimePoint timePoint;
TimePointSpecificationFoundInLogImpl() {} // for GWT serialization
public TimePointSpecificationFoundInLogImpl(TimePoint timePoint) {
super();
this.timePoint = timePoint;
}
@Override
public TimePoint getTimePoint() {
return timePoint;
}
}
@@ -17,7 +17,9 @@ public interface CompetitorDTO extends Serializable, MappableToDevice {
String getIdAsString();
String getSailID();
String getSearchTag();
BoatDTO getBoat();
BoatClassDTO getBoatClass();
@@ -1,207 +1,219 @@
package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import com.sap.sse.common.Color;
import com.sap.sse.common.Duration;
/**
* Equality and hash code are based on the {@link #getIdAsString() ID}, the {@link #getSailID() sail number}, the
* {@link #getBoatClass() boat class} (whose equality and hash code, in turn, depends on its name) and the
* {@link #getThreeLetterIocCountryCode() IOC country code}. Note that the three latter properties are subject
* to change for a competitor while the ID remains unchanged.
*
* @author Axel Uhl (d043530)
*
*/
public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serializable {
private static final long serialVersionUID = -4997852354821083154L;
private String countryName;
private String twoLetterIsoCountryCode;
private String threeLetterIocCountryCode;
private Color color;
private String email;
private String idAsString;
private BoatClassDTO boatClass;
private BoatDTO boat;
private String imageURL;
private String flagImageURL;
private Double timeOnTimeFactor;
private Duration timeOnDistanceAllowancePerNauticalMile;
public CompetitorDTOImpl() {}
public CompetitorDTOImpl(String name, Color color, String email, String twoLetterIsoCountryCode, String threeLetterIocCountryCode,
String countryName, String idAsString, String imageURL, String flagImageURL,
BoatDTO boat, BoatClassDTO boatClass, Double timeOnTimeFactor, Duration timeOnDistanceAllowancePerNauticalMile) {
super(name);
this.color = color;
this.email = email;
this.twoLetterIsoCountryCode = twoLetterIsoCountryCode;
this.threeLetterIocCountryCode = threeLetterIocCountryCode;
this.countryName = countryName;
this.idAsString = idAsString;
this.imageURL = imageURL;
this.flagImageURL = flagImageURL;
this.boat = boat;
this.boatClass = boatClass;
this.timeOnTimeFactor = timeOnTimeFactor;
this.timeOnDistanceAllowancePerNauticalMile = timeOnDistanceAllowancePerNauticalMile;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((boatClass == null) ? 0 : boatClass.hashCode());
result = prime * result + ((boat == null) ? 0 : boat.hashCode());
result = prime * result + ((idAsString == null) ? 0 : idAsString.hashCode());
result = prime * result + ((color == null) ? 0 : color.hashCode());
result = prime * result + ((email == null) ? 0 : email.hashCode());
result = prime * result + ((threeLetterIocCountryCode == null) ? 0 : threeLetterIocCountryCode.hashCode());
result = prime * result + ((imageURL == null) ? 0 : imageURL.hashCode());
result = prime * result + ((flagImageURL == null) ? 0 : flagImageURL.hashCode());
result = prime * result + ((timeOnTimeFactor == null) ? 0 : timeOnTimeFactor.hashCode());
result = prime * result + ((timeOnDistanceAllowancePerNauticalMile == null) ? 0 : timeOnDistanceAllowancePerNauticalMile.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
CompetitorDTOImpl other = (CompetitorDTOImpl) obj;
if (boatClass == null) {
if (other.boatClass != null)
return false;
} else if (!boatClass.equals(other.boatClass))
return false;
if (boat == null) {
if (other.boat != null)
return false;
} else if (!boat.equals(other.boat))
return false;
if (idAsString == null) {
if (other.idAsString != null)
return false;
} else if (!idAsString.equals(other.idAsString))
return false;
if (threeLetterIocCountryCode == null) {
if (other.threeLetterIocCountryCode != null)
return false;
} else if (!threeLetterIocCountryCode.equals(other.threeLetterIocCountryCode))
return false;
if (color == null) {
if (other.color != null)
return false;
} else if (!color.equals(other.color))
return false;
if (email == null) {
if (other.email != null)
return false;
} else if (!email.equals(other.email))
return false;
if (imageURL == null) {
if (other.imageURL != null)
return false;
} else if (!imageURL.equals(other.imageURL))
return false;
if (flagImageURL == null) {
if (other.flagImageURL != null)
return false;
} else if (!flagImageURL.equals(other.flagImageURL))
return false;
if (timeOnTimeFactor == null) {
if (other.timeOnTimeFactor != null)
return false;
} else if (!timeOnTimeFactor.equals(other.timeOnTimeFactor))
return false;
if (timeOnDistanceAllowancePerNauticalMile == null) {
if (other.timeOnDistanceAllowancePerNauticalMile != null)
return false;
} else if (!timeOnDistanceAllowancePerNauticalMile.equals(other.timeOnDistanceAllowancePerNauticalMile))
return false;
return true;
}
@Override
public String getTwoLetterIsoCountryCode() {
return twoLetterIsoCountryCode;
}
@Override
public String getThreeLetterIocCountryCode() {
return threeLetterIocCountryCode;
}
@Override
public String getCountryName() {
return countryName;
}
@Override
public String getSailID() {
return boat==null?null:boat.getSailId();
}
@Override
public String getImageURL() {
return imageURL;
}
@Override
public String getFlagImageURL() {
return flagImageURL;
}
@Override
public String getIdAsString() {
return idAsString;
}
@Override
public BoatClassDTO getBoatClass() {
return boatClass;
}
@Override
public CompetitorDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion) {
return this;
}
public Color getColor() {
return color;
}
public String getEmail() {
return email;
}
public String email(){
return email;
}
@Override
public boolean hasEmail() {
return email != null && !email.isEmpty();
}
@Override
public Double getTimeOnTimeFactor() {
return timeOnTimeFactor;
}
@Override
public Duration getTimeOnDistanceAllowancePerNauticalMile() {
return timeOnDistanceAllowancePerNauticalMile;
}
@Override
public BoatDTO getBoat() {
return boat;
}
}
package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import com.sap.sse.common.Color;
import com.sap.sse.common.Duration;
/**
* Equality and hash code are based on the {@link #getIdAsString() ID}, the {@link #getSailID() sail number}, the
* {@link #getBoatClass() boat class} (whose equality and hash code, in turn, depends on its name) and the
* {@link #getThreeLetterIocCountryCode() IOC country code}. Note that the three latter properties are subject
* to change for a competitor while the ID remains unchanged.
*
* @author Axel Uhl (d043530)
*
*/
public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serializable {
private static final long serialVersionUID = -4997852354821083154L;
private String countryName;
private String twoLetterIsoCountryCode;
private String threeLetterIocCountryCode;
private Color color;
private String email;
private String searchTag;
private String idAsString;
private BoatClassDTO boatClass;
private BoatDTO boat;
private String imageURL;
private String flagImageURL;
private Double timeOnTimeFactor;
private Duration timeOnDistanceAllowancePerNauticalMile;
public CompetitorDTOImpl() {}
public CompetitorDTOImpl(String name, Color color, String email, String twoLetterIsoCountryCode, String threeLetterIocCountryCode,
String countryName, String idAsString, String imageURL, String flagImageURL,
BoatDTO boat, BoatClassDTO boatClass, Double timeOnTimeFactor, Duration timeOnDistanceAllowancePerNauticalMile, String searchTag) {
super(name);
this.color = color;
this.email = email;
this.twoLetterIsoCountryCode = twoLetterIsoCountryCode;
this.threeLetterIocCountryCode = threeLetterIocCountryCode;
this.countryName = countryName;
this.idAsString = idAsString;
this.imageURL = imageURL;
this.flagImageURL = flagImageURL;
this.boat = boat;
this.boatClass = boatClass;
this.timeOnTimeFactor = timeOnTimeFactor;
this.timeOnDistanceAllowancePerNauticalMile = timeOnDistanceAllowancePerNauticalMile;
this.searchTag = searchTag;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((boatClass == null) ? 0 : boatClass.hashCode());
result = prime * result + ((boat == null) ? 0 : boat.hashCode());
result = prime * result + ((idAsString == null) ? 0 : idAsString.hashCode());
result = prime * result + ((color == null) ? 0 : color.hashCode());
result = prime * result + ((email == null) ? 0 : email.hashCode());
result = prime * result + ((threeLetterIocCountryCode == null) ? 0 : threeLetterIocCountryCode.hashCode());
result = prime * result + ((imageURL == null) ? 0 : imageURL.hashCode());
result = prime * result + ((flagImageURL == null) ? 0 : flagImageURL.hashCode());
result = prime * result + ((timeOnTimeFactor == null) ? 0 : timeOnTimeFactor.hashCode());
result = prime * result + ((timeOnDistanceAllowancePerNauticalMile == null) ? 0 : timeOnDistanceAllowancePerNauticalMile.hashCode());
result = prime * result + ((searchTag == null) ? 0 : searchTag.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
CompetitorDTOImpl other = (CompetitorDTOImpl) obj;
if (boatClass == null) {
if (other.boatClass != null)
return false;
} else if (!boatClass.equals(other.boatClass))
return false;
if (boat == null) {
if (other.boat != null)
return false;
} else if (!boat.equals(other.boat))
return false;
if (idAsString == null) {
if (other.idAsString != null)
return false;
} else if (!idAsString.equals(other.idAsString))
return false;
if (threeLetterIocCountryCode == null) {
if (other.threeLetterIocCountryCode != null)
return false;
} else if (!threeLetterIocCountryCode.equals(other.threeLetterIocCountryCode))
return false;
if (color == null) {
if (other.color != null)
return false;
} else if (!color.equals(other.color))
return false;
if (email == null) {
if (other.email != null)
return false;
} else if (!email.equals(other.email))
return false;
if (imageURL == null) {
if (other.imageURL != null)
return false;
} else if (!imageURL.equals(other.imageURL))
return false;
if (flagImageURL == null) {
if (other.flagImageURL != null)
return false;
} else if (!flagImageURL.equals(other.flagImageURL))
return false;
if (timeOnTimeFactor == null) {
if (other.timeOnTimeFactor != null)
return false;
} else if (!timeOnTimeFactor.equals(other.timeOnTimeFactor))
return false;
if (timeOnDistanceAllowancePerNauticalMile == null) {
if (other.timeOnDistanceAllowancePerNauticalMile != null)
return false;
} else if (!timeOnDistanceAllowancePerNauticalMile.equals(other.timeOnDistanceAllowancePerNauticalMile))
return false;
if (searchTag == null) {
if (other.searchTag != null)
return false;
} else if (!searchTag.equals(other.searchTag))
return false;
return true;
}
@Override
public String getTwoLetterIsoCountryCode() {
return twoLetterIsoCountryCode;
}
@Override
public String getThreeLetterIocCountryCode() {
return threeLetterIocCountryCode;
}
@Override
public String getCountryName() {
return countryName;
}
@Override
public String getSailID() {
return boat==null?null:boat.getSailId();
}
@Override
public String getImageURL() {
return imageURL;
}
@Override
public String getFlagImageURL() {
return flagImageURL;
}
@Override
public String getIdAsString() {
return idAsString;
}
@Override
public BoatClassDTO getBoatClass() {
return boatClass;
}
@Override
public CompetitorDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion) {
return this;
}
public Color getColor() {
return color;
}
public String getSearchTag() {
return searchTag;
}
public String getEmail() {
return email;
}
public String email(){
return email;
}
@Override
public boolean hasEmail() {
return email != null && !email.isEmpty();
}
@Override
public Double getTimeOnTimeFactor() {
return timeOnTimeFactor;
}
@Override
public Duration getTimeOnDistanceAllowancePerNauticalMile() {
return timeOnDistanceAllowancePerNauticalMile;
}
@Override
public BoatDTO getBoat() {
return boat;
}
}
@@ -1,143 +1,149 @@
package com.sap.sailing.domain.common.dto;
import com.sap.sse.common.Color;
import com.sap.sse.common.Duration;
/**
* Equality and hash code are based on the index pointing into a previous leaderboard's competitors list.
*
* @author Axel Uhl (d043530)
*
*/
public class PreviousCompetitorDTOImpl implements CompetitorDTO {
private static final long serialVersionUID = 8820028699103040805L;
private int indexInPreviousCompetitorList;
PreviousCompetitorDTOImpl() {} // for serialization only
public PreviousCompetitorDTOImpl(int indexInPreviousCompetitorList) {
super();
this.indexInPreviousCompetitorList = indexInPreviousCompetitorList;
}
protected int getIndexInPreviousCompetitorList() {
return indexInPreviousCompetitorList;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + indexInPreviousCompetitorList;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PreviousCompetitorDTOImpl other = (PreviousCompetitorDTOImpl) obj;
if (indexInPreviousCompetitorList != other.indexInPreviousCompetitorList)
return false;
return true;
}
@Override
public String getTwoLetterIsoCountryCode() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getThreeLetterIocCountryCode() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getCountryName() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getSailID() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public BoatDTO getBoat() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public Color getColor() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getIdAsString() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public BoatClassDTO getBoatClass() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getName() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getEmail() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getImageURL() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getFlagImageURL() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public boolean hasEmail() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public Double getTimeOnTimeFactor() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public Duration getTimeOnDistanceAllowancePerNauticalMile() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public CompetitorDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion) {
return previousVersion.competitors.get(indexInPreviousCompetitorList);
}
package com.sap.sailing.domain.common.dto;
import com.sap.sse.common.Color;
import com.sap.sse.common.Duration;
/**
* Equality and hash code are based on the index pointing into a previous leaderboard's competitors list.
*
* @author Axel Uhl (d043530)
*
*/
public class PreviousCompetitorDTOImpl implements CompetitorDTO {
private static final long serialVersionUID = 8820028699103040805L;
private int indexInPreviousCompetitorList;
PreviousCompetitorDTOImpl() {} // for serialization only
public PreviousCompetitorDTOImpl(int indexInPreviousCompetitorList) {
super();
this.indexInPreviousCompetitorList = indexInPreviousCompetitorList;
}
protected int getIndexInPreviousCompetitorList() {
return indexInPreviousCompetitorList;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + indexInPreviousCompetitorList;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PreviousCompetitorDTOImpl other = (PreviousCompetitorDTOImpl) obj;
if (indexInPreviousCompetitorList != other.indexInPreviousCompetitorList)
return false;
return true;
}
@Override
public String getTwoLetterIsoCountryCode() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getThreeLetterIocCountryCode() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getCountryName() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getSailID() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public BoatDTO getBoat() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public Color getColor() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getIdAsString() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public BoatClassDTO getBoatClass() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getName() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getEmail() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getSearchTag() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getImageURL() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public String getFlagImageURL() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public boolean hasEmail() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public Double getTimeOnTimeFactor() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public Duration getTimeOnDistanceAllowancePerNauticalMile() {
throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+
" need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization");
}
@Override
public CompetitorDTO getCompetitorFromPrevious(LeaderboardDTO previousVersion) {
return previousVersion.competitors.get(indexInPreviousCompetitorList);
}
}
@@ -129,6 +129,24 @@ public class RaceColumnDTO extends NamedDTO implements Serializable {
return start;
}
public Date getStartOfTracking(FleetDTO fleet){
Date start = null;
RaceDTO raceDTO = racesPerFleet.get(fleet);
if (raceDTO != null && raceDTO.isTracked) {
start = raceDTO.trackedRace.startOfTracking;
}
return start;
}
public Date getEndOfTracking(FleetDTO fleet){
Date start = null;
RaceDTO raceDTO = racesPerFleet.get(fleet);
if (raceDTO != null && raceDTO.isTracked) {
start = raceDTO.trackedRace.endOfTracking;
}
return start;
}
/**
* @return The {@link PlacemarkOrderDTO places} or <code>null</code>, if no places are available
*/
@@ -1,21 +1,23 @@
package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState;
public class RaceLogTrackingInfoDTO implements Serializable {
private static final long serialVersionUID = -6861382173074620168L;
public boolean raceLogTrackerExists;
public boolean competitorRegistrationsExists;
public RaceLogTrackingState raceLogTrackingState;
protected RaceLogTrackingInfoDTO() {}
public RaceLogTrackingInfoDTO(boolean raceLogTrackerExists, boolean competitorRegistrationsExists,
RaceLogTrackingState raceLogTrackingState) {
this.raceLogTrackerExists = raceLogTrackerExists;
this.competitorRegistrationsExists = competitorRegistrationsExists;
this.raceLogTrackingState = raceLogTrackingState;
}
}
package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState;
public class RaceLogTrackingInfoDTO implements Serializable {
private static final long serialVersionUID = -6861382173074620168L;
public boolean raceLogTrackerExists;
public boolean competitorRegistrationsExists;
public boolean courseExists;
public RaceLogTrackingState raceLogTrackingState;
protected RaceLogTrackingInfoDTO() {}
public RaceLogTrackingInfoDTO(boolean raceLogTrackerExists, boolean competitorRegistrationsExists, boolean courseExists,
RaceLogTrackingState raceLogTrackingState) {
this.raceLogTrackerExists = raceLogTrackerExists;
this.competitorRegistrationsExists = competitorRegistrationsExists;
this.courseExists = courseExists;
this.raceLogTrackingState = raceLogTrackingState;
}
}
@@ -1,58 +1,65 @@
package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.List;
public class SeriesCreationParametersDTO implements Serializable {
private static final long serialVersionUID = -3205172230707607515L;
private List<FleetDTO> fleets;
private boolean medal;
private boolean startsWithZero;
private boolean firstColumnIsNonDiscardableCarryForward;
private int[] discardingThresholds;
private boolean hasSplitFleetContiguousScoring;
SeriesCreationParametersDTO() {}
public SeriesCreationParametersDTO(List<FleetDTO> fleets, boolean medal, boolean startsWithZero, boolean firstColumnIsNonDiscardableCarryForward,
int[] discardingThresholds, boolean hasSplitFleetContiguousScoring) {
super();
this.fleets = fleets;
this.medal = medal;
this.startsWithZero = startsWithZero;
this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring;
this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward;
this.discardingThresholds = discardingThresholds;
}
public List<FleetDTO> getFleets() {
return fleets;
}
public boolean isMedal() {
return medal;
}
public boolean isStartsWithZero() {
return startsWithZero;
}
public boolean hasSplitFleetContiguousScoring() {
return hasSplitFleetContiguousScoring;
}
public int[] getDiscardingThresholds() {
return discardingThresholds;
}
public boolean isFirstColumnIsNonDiscardableCarryForward() {
return firstColumnIsNonDiscardableCarryForward;
}
}
package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.List;
public class SeriesCreationParametersDTO implements Serializable {
private static final long serialVersionUID = -3205172230707607515L;
private List<FleetDTO> fleets;
private boolean isMedal;
private boolean isFleetsCanRunInParallel;
private boolean isStartsWithZeroScore;
private boolean firstColumnIsNonDiscardableCarryForward;
private int[] discardingThresholds;
private boolean hasSplitFleetContiguousScoring;
SeriesCreationParametersDTO() {}
public SeriesCreationParametersDTO(List<FleetDTO> fleets, boolean isMedal, boolean isFleetsCanRunInParallel, boolean isStartsWithZeroScore, boolean firstColumnIsNonDiscardableCarryForward,
int[] discardingThresholds, boolean hasSplitFleetContiguousScoring) {
super();
this.fleets = fleets;
this.isMedal = isMedal;
this.isFleetsCanRunInParallel = isFleetsCanRunInParallel;
this.isStartsWithZeroScore = isStartsWithZeroScore;
this.hasSplitFleetContiguousScoring = hasSplitFleetContiguousScoring;
this.firstColumnIsNonDiscardableCarryForward = firstColumnIsNonDiscardableCarryForward;
this.discardingThresholds = discardingThresholds;
}
public List<FleetDTO> getFleets() {
return fleets;
}
public boolean isMedal() {
return isMedal;
}
public boolean isStartsWithZero() {
return isStartsWithZeroScore;
}
public boolean isFleetsCanRunInParallel() {
return isFleetsCanRunInParallel;
}
public boolean hasSplitFleetContiguousScoring() {
return hasSplitFleetContiguousScoring;
}
public int[] getDiscardingThresholds() {
return discardingThresholds;
}
public boolean isFirstColumnIsNonDiscardableCarryForward() {
return firstColumnIsNonDiscardableCarryForward;
}
}
@@ -0,0 +1,17 @@
package com.sap.sailing.domain.common.i18n;
public interface CommonStringMessages {
/* Master data import sub progress messages */
String init();
String connectionSetup();
String connectionEstablish();
String transferStarted();
String transferCompleted();
String importInit();
String importWait();
String importLeaderboardGroups();
String importWindTracks();
String updateEventLeaderboardGroupLinks();
}
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.common.impl;
import java.util.UUID;
import com.sap.sailing.domain.common.DataImportProgress;
import com.sap.sailing.domain.common.DataImportSubProgress;
import com.sap.sailing.domain.common.MasterDataImportObjectCreationCount;
public class DataImportProgressImpl implements DataImportProgress {
@@ -11,7 +12,7 @@ public class DataImportProgressImpl implements DataImportProgress {
private UUID currentImportOperationId;
private MasterDataImportObjectCreationCount result = null;
private double overallProgressPct = 0;
private String currentSubProgressName = "Initializing Import";
private DataImportSubProgress currentSubProgress = DataImportSubProgress.IMPORT_INIT;
private double currentSubProgressPct = 0;
private boolean failed = false;
private String errorMessage;
@@ -21,17 +22,16 @@ public class DataImportProgressImpl implements DataImportProgress {
public DataImportProgressImpl(final UUID currentImportOperationId) {
this.currentImportOperationId = currentImportOperationId;
}
@Override
public double getOverallProgressPct() {
return overallProgressPct;
}
@Override
public String getNameOfCurrentSubProgress() {
return currentSubProgressName;
public DataImportSubProgress getCurrentSubProgress() {
return currentSubProgress;
}
@Override
@@ -48,10 +48,10 @@ public class DataImportProgressImpl implements DataImportProgress {
public void setOverAllProgressPct(double pct) {
overallProgressPct = pct;
}
@Override
public void setNameOfCurrentSubProgress(String name) {
currentSubProgressName = name;
public void setCurrentSubProgress(DataImportSubProgress subProgress) {
currentSubProgress = subProgress;
}
@Override
@@ -0,0 +1,12 @@
package com.sap.sailing.domain.common.impl;
public class RaceColumnConstants {
public static final String LEADERBOARD_NAME = "leaderboard_name";
public static final String LEADERBOARD_DISPLAY_NAME = "leaderboard_display_name";
public static final String RACE_COLUMNS = "race_columns";
public static final String RACE_COLUMN_NAME = "race_column_name";
public static final String EXPLICIT_FACTOR = "explicit_factor";
public static final String FACTOR = "factor";
}
@@ -14,6 +14,11 @@ public enum RaceLogRaceStatus {
RaceLogRaceStatus(int orderNumber) {
this.orderNumber = orderNumber;
}
public static boolean isPreRunning(RaceLogRaceStatus status) {
return status != null &&
(status.equals(SCHEDULED) || status.equals(STARTPHASE));
}
public static boolean isActive(RaceLogRaceStatus status) {
return status != null &&
@@ -21,6 +26,11 @@ public enum RaceLogRaceStatus {
}
public static boolean isRunningOrFinished(RaceLogRaceStatus status) {
return status != null &&
(status.equals(STARTPHASE) || status.equals(RUNNING) || status.equals(FINISHING) || status.equals(FINISHED));
}
public int getOrderNumber() {
return orderNumber;
}
@@ -1,6 +1,5 @@
package com.sap.sailing.domain.common.racelog.tracking;
import com.sap.sailing.domain.common.racelog.RaceLogServletConstants;
import com.sap.sse.common.util.UrlHelper;
/**
@@ -39,44 +38,16 @@ public class DeviceMappingConstants {
public static final String JSON_TO_MILLIS = "toMillis";
public static final String JSON_TEAM_IMAGE_URI = "teamImageUri";
@Deprecated
static final String APK_PATH = "/apps/com.sap.sailing.android.tracking.app.apk";
public static String getDeviceMappingForRegattaLogUrl(String serverUrlWithoutTrailingSlash, String eventId,
String leaderboardName, String mappedItemType, String mappedItemId, UrlHelper helper) {
return helper.encodeUrl(serverUrlWithoutTrailingSlash + TRACKING_URL_BASE + "?" + URL_EVENT_ID + "=" + eventId + "&"
+ URL_LEADERBOARD_NAME + "=" + leaderboardName + "&" + mappedItemType + "="
+ mappedItemId);
return serverUrlWithoutTrailingSlash + TRACKING_URL_BASE + "?" + URL_EVENT_ID + "=" + helper.encodeQueryString(eventId) + "&"
+ URL_LEADERBOARD_NAME + "=" + helper.encodeQueryString(leaderboardName) + "&" + helper.encodeQueryString(mappedItemType) + "="
+ helper.encodeQueryString(mappedItemId);
}
public static String getBuoyTenderInvitationUrl(String serverUrlWithoutTrailingSlash,
String leaderboardName, String eventId, UrlHelper helper) {
return helper.encodeUrl(serverUrlWithoutTrailingSlash + BUOY_TENDER_URL_BASE + "?" + URL_EVENT_ID + "="
+ eventId + "&" + URL_LEADERBOARD_NAME + "=" + leaderboardName);
}
@Deprecated
public static String getDeviceMappingForRaceLogUrl(String serverUrlWithoutTrailingSlash, String leaderboardName,
String raceColumnName, String fleetName, String mappedItemType, String mappedItemId, Long fromMillis,
Long toMillis, UrlHelper helper) throws QRCodeURLCreationException {
if (toMillis != null && fromMillis != null){
if (fromMillis > toMillis) {
throw new QRCodeURLCreationException("from can't lie after to");
}
}
StringBuilder urlToEncode = new StringBuilder();
urlToEncode.append(serverUrlWithoutTrailingSlash + DeviceMappingConstants.APK_PATH + "?"
+ RaceLogServletConstants.PARAMS_LEADERBOARD_NAME + "=" + leaderboardName + "&"
+ RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME + "=" + raceColumnName + "&"
+ RaceLogServletConstants.PARAMS_RACE_FLEET_NAME + "=" + fleetName + "&" + mappedItemType + "="
+ mappedItemId);
urlToEncode.append((fromMillis != null) ? "&" + DeviceMappingConstants.URL_FROM_MILLIS + "=" + fromMillis : "");
urlToEncode.append((toMillis != null) ? "&" + DeviceMappingConstants.URL_TO_MILLIS + "=" + toMillis : "");
return helper.encodeUrl(urlToEncode.toString());
return serverUrlWithoutTrailingSlash + BUOY_TENDER_URL_BASE + "?" + URL_EVENT_ID + "="
+ helper.encodeQueryString(eventId) + "&" + URL_LEADERBOARD_NAME + "=" + helper.encodeQueryString(leaderboardName);
}
}
@@ -1,10 +1,19 @@
package com.sap.sailing.domain.common.tracking;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Positioned;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sse.common.Timed;
public interface GPSFix extends Positioned, Timed, WithValidityCache, WithEstimatedSpeedCache {
/**
* A typical horizontal dilution of precision, or in other words, an error that is quite likely and may occur.
* For example, when trying to tell whether an object was on the one or the other side of some other object,
* this typical level of accuracy can be used as a default margin of error.
*/
Distance TYPICAL_HDOP = new MeterDistance(5);
/**
* Reaches <code>to</code> at <code>to</code>'s {@link GPSFix#getTimePoint() time point} starting at this fix,
* traveling on a straight great circle segment.
@@ -22,7 +22,9 @@ public class CompetitorJsonConstants {
public static final String FIELD_BOAT = "boat";
public static final String FIELD_DISPLAY_COLOR = "displayColor";
public static final String FIELD_EMAIL = "email";
public static final String FIELD_SEARCHTAG = "searchTag";
public static final String FIELD_FLAG_IMAGE_URI = "flagImageUri";
public static final String FIELD_TEAM_IMAGE_URI = "teamImageUri";
public static final String FIELD_TIME_ON_TIME_FACTOR = "timeOnTimeFactor";
public static final String FIELD_TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE = "timeOnDistanceAllowanceInSecondsPerNauticalMile";
}
@@ -37,7 +37,6 @@ public class GPSFixImpl extends AbstractGPSFixImpl implements GPSFix {
public String toString() {
return getTimePoint()+": "+getPosition();
}
public static GPSFixImpl create(double lonDeg, double latDeg, long timeMillis) {
return new GPSFixImpl(new DegreePosition(latDeg, lonDeg), new MillisecondsTimePoint(timeMillis));
@@ -28,7 +28,9 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import com.sap.sailing.domain.igtimiadapter.Account;
import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver;
@@ -47,6 +49,8 @@ import com.sap.sse.mongodb.MongoDBService;
public class WebSocketTest {
private static final Logger logger = Logger.getLogger(WebSocketTest.class.getName());
@Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(1 * 60 * 1000);
@WebSocket
public class SimpleEchoTestSocket {
private final CountDownLatch closeLatch;
@@ -8,7 +8,6 @@ import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -501,14 +500,7 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
result.add(uri);
}
// sort those to the front that don't do port 443 nor wss://
Collections.sort(result, new Comparator<URI>() {
@Override
public int compare(URI o1, URI o2) {
int o1Score = (o1.toString().contains("443") ? 1 : 0) + (o1.toString().contains("wss") ? 1 : 0);
int o2Score = (o2.toString().contains("443") ? 1 : 0) + (o2.toString().contains("wss") ? 1 : 0);
return o1Score - o2Score;
}
});
Collections.shuffle(result); // shuffle as a failover strategy
logger.info("Trying Igtimi WebSocket servers in the following order: "+result);
return result;
}
@@ -143,6 +143,7 @@ import com.sap.sailing.domain.base.impl.SailingServerConfigurationImpl;
import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.base.impl.VenueImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.CourseDesignerMode;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.PassingInstruction;
@@ -1188,6 +1189,12 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
private Series loadSeries(DBObject dbSeries, TrackedRegattaRegistry trackedRegattaRegistry) {
String name = (String) dbSeries.get(FieldNames.SERIES_NAME.name());
boolean isMedal = (Boolean) dbSeries.get(FieldNames.SERIES_IS_MEDAL.name());
// isFleetCanRunInParallel is a new field -> set to true when it does not exist in older db versions
boolean isFleetsCanRunInParallel = true;
Object isFleetCanRunInParallelObject = dbSeries.get(FieldNames.SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL.name());
if (isFleetCanRunInParallelObject != null) {
isFleetsCanRunInParallel = (Boolean) dbSeries.get(FieldNames.SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL.name());
}
Boolean startsWithZeroScore = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name());
Boolean hasSplitFleetContiguousScoring = (Boolean) dbSeries.get(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name());
Boolean firstColumnIsNonDiscardableCarryForward = (Boolean) dbSeries.get(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name());
@@ -1195,7 +1202,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
List<Fleet> fleets = loadFleets(dbFleets);
BasicDBList dbRaceColumns = (BasicDBList) dbSeries.get(FieldNames.SERIES_RACE_COLUMNS.name());
Iterable<String> raceColumnNames = loadRaceColumnNames(dbRaceColumns);
Series series = new SeriesImpl(name, isMedal, fleets, raceColumnNames, trackedRegattaRegistry);
Series series = new SeriesImpl(name, isMedal, isFleetsCanRunInParallel, fleets, raceColumnNames, trackedRegattaRegistry);
if (dbSeries.get(FieldNames.SERIES_DISCARDING_THRESHOLDS.name()) != null) {
ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbSeries, FieldNames.SERIES_DISCARDING_THRESHOLDS);
series.setResultDiscardingRule(resultDiscardingRule);
@@ -1498,7 +1505,9 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
private RaceLogCourseDesignChangedEvent loadRaceLogCourseDesignChangedEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
String courseName = (String) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGN_NAME.name());
CourseBase courseData = loadCourseData((BasicDBList) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGN.name()), courseName);
return new RaceLogCourseDesignChangedEventImpl(createdAt, logicalTimePoint, author, id, passId, courseData);
final String courseDesignerModeName = (String) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGNER_MODE.name());
final CourseDesignerMode courseDesignerMode = courseDesignerModeName == null ? null : CourseDesignerMode.valueOf(courseDesignerModeName);
return new RaceLogCourseDesignChangedEventImpl(createdAt, logicalTimePoint, author, id, passId, courseData, courseDesignerMode);
}
private RaceLogCourseAreaChangedEvent loadRaceLogCourseAreaChangedEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List<Competitor> competitors, DBObject dbObject) {
@@ -1615,8 +1624,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
loadRegattaLogEvents(result, query, identifier);
} catch (Throwable t) {
// something went wrong during DB access; report, then use empty new regatta log
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded regatta log data. Check MongoDB settings.");
logger.log(Level.SEVERE, "loadRegattaLog", t);
logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded regatta log data for "+identifier+". Check MongoDB settings.", t);
}
return result;
}
@@ -1708,7 +1716,15 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
private RegattaLogRegisterCompetitorEvent loadRegattaLogRegisterCompetitorEvent(TimePoint createdAt, AbstractLogEventAuthor author,
TimePoint logicalTimePoint, Serializable id, DBObject dbObject) {
Competitor comp = getCompetitorByID(dbObject);
return new RegattaLogRegisterCompetitorEventImpl(createdAt, logicalTimePoint, author, id, comp);
final RegattaLogRegisterCompetitorEvent result;
if (comp == null) {
result = null;
logger.log(Level.SEVERE, "Couldn't resolve competitor with ID "+dbObject.get(FieldNames.REGATTA_LOG_COMPETITOR_ID.name())+
" from registration event with ID "+id+". Skipping this competitor registration.");
} else {
result = new RegattaLogRegisterCompetitorEventImpl(createdAt, logicalTimePoint, author, id, comp);
}
return result;
}
private RegattaLogCloseOpenEndedDeviceMappingEvent loadRegattaLogCloseOpenEndedDeviceMappingEvent(TimePoint createdAt,
@@ -1795,23 +1811,31 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
logger.log(Level.WARNING, "Could not load deviceId for RaceLogEvent", e);
e.printStackTrace();
}
final Serializable competitorId = (Serializable) dbObject.get(FieldNames.COMPETITOR_ID.name());
Competitor mappedTo = baseDomainFactory.getExistingCompetitorById(
(Serializable) dbObject.get(FieldNames.COMPETITOR_ID.name()));
@SuppressWarnings("deprecation") // used only for auto-migration; may be removed in future releases
final FieldNames deprecatedFromFieldName = FieldNames.RACE_LOG_FROM;
@SuppressWarnings("deprecation") // used only for auto-migration; may be removed in future releases
final FieldNames deprecatedToFieldName = FieldNames.RACE_LOG_TO;
Triple<TimePoint, TimePoint, Boolean> times = loadFromToTimePoint(dbObject, FieldNames.REGATTA_LOG_FROM, deprecatedFromFieldName, FieldNames.REGATTA_LOG_TO, deprecatedToFieldName);
final TimePoint from = times.getA();
final TimePoint to = times.getB();
final boolean needsMigration = times.getC();
final RegattaLogDeviceCompetitorMappingEventImpl result = new RegattaLogDeviceCompetitorMappingEventImpl(createdAt, logicalTimePoint, author, id, mappedTo, device, from, to);
if (needsMigration) {
// remove old version of mapping event
WriteResult removeResult = database.getCollection(CollectionNames.REGATTA_LOGS.name()).remove(outerDBObject);
assert removeResult.getN() == 1;
// and then insert using the fixed storage implementation
new MongoObjectFactoryImpl(database, serviceFinderFactory).storeRegattaLogEvent(regattaLogIdentifier, result);
competitorId);
final RegattaLogDeviceCompetitorMappingEventImpl result;
if (mappedTo == null) {
logger.severe("Found a "+RegattaLogDeviceCompetitorMappingEventImpl.class.getName()+
" event but couldn't find competitor with ID "+competitorId);
result = null;
} else {
@SuppressWarnings("deprecation") // used only for auto-migration; may be removed in future releases
final FieldNames deprecatedFromFieldName = FieldNames.RACE_LOG_FROM;
@SuppressWarnings("deprecation") // used only for auto-migration; may be removed in future releases
final FieldNames deprecatedToFieldName = FieldNames.RACE_LOG_TO;
Triple<TimePoint, TimePoint, Boolean> times = loadFromToTimePoint(dbObject, FieldNames.REGATTA_LOG_FROM, deprecatedFromFieldName, FieldNames.REGATTA_LOG_TO, deprecatedToFieldName);
final TimePoint from = times.getA();
final TimePoint to = times.getB();
final boolean needsMigration = times.getC();
result = new RegattaLogDeviceCompetitorMappingEventImpl(createdAt, logicalTimePoint, author, id, mappedTo, device, from, to);
if (needsMigration) {
// remove old version of mapping event
WriteResult removeResult = database.getCollection(CollectionNames.REGATTA_LOGS.name()).remove(outerDBObject);
assert removeResult.getN() == 1;
// and then insert using the fixed storage implementation
new MongoObjectFactoryImpl(database, serviceFinderFactory).storeRegattaLogEvent(regattaLogIdentifier, result);
}
}
return result;
}
@@ -36,6 +36,7 @@ public enum FieldNames {
REGATTA_USE_START_TIME_INFERENCE,
SERIES_NAME,
SERIES_IS_MEDAL,
SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL,
SERIES_STARTS_WITH_ZERO_SCORE,
SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING,
SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD,
@@ -88,7 +89,7 @@ public enum FieldNames {
// race log
RACE_LOG_IDENTIFIER, RACE_LOG_EVENT, RACE_LOG_EVENT_PASS_ID, RACE_LOG_EVENT_ID, RACE_LOG_EVENT_CREATED_AT, RACE_LOG_EVENT_AUTHOR_NAME,
RACE_LOG_EVENT_AUTHOR_PRIORITY, RACE_LOG_EVENT_INVOLVED_BOATS, RACE_LOG_EVENT_FLAG_UPPER, RACE_LOG_EVENT_FLAG_LOWER, RACE_LOG_EVENT_FLAG_DISPLAYED,
RACE_LOG_EVENT_START_TIME, RACE_LOG_EVENT_CLASS, RACE_LOG_EVENT_NEXT_STATUS, RACE_LOG_COURSE_DESIGN, WAYPOINT_ID, WAYPOINT_NAME,
RACE_LOG_EVENT_START_TIME, RACE_LOG_EVENT_CLASS, RACE_LOG_EVENT_NEXT_STATUS, RACE_LOG_COURSE_DESIGN, RACE_LOG_COURSE_DESIGNER_MODE, WAYPOINT_ID, WAYPOINT_NAME,
WAYPOINT_PASSINGINSTRUCTIONS, CONTROLPOINT, CONTROLPOINT_CLASS, MARK_ID, MARK_COLOR, MARK_NAME, MARK_PATTERN, MARK_SHAPE, MARK_TYPE,
CONTROLPOINT_VALUE, CONTROLPOINTWITHTWOMARKS_ID, CONTROLPOINTWITHTWOMARKS_NAME, CONTROLPOINTWITHTWOMARKS_LEFT, CONTROLPOINTWITHTWOMARKS_RIGHT,
RACE_LOG_POSITIONED_COMPETITORS, RACE_LOG_PATHFINDER_ID, RACE_LOG_GATE_LINE_OPENING_TIME, RACE_LOG_GOLF_DOWN_TIME, RACE_LOG_START_PROCEDURE_TYPE,
@@ -674,6 +674,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
DBObject dbSeries = new BasicDBObject();
dbSeries.put(FieldNames.SERIES_NAME.name(), s.getName());
dbSeries.put(FieldNames.SERIES_IS_MEDAL.name(), s.isMedal());
dbSeries.put(FieldNames.SERIES_IS_FLEETS_CAN_RUN_IN_PARALLEL.name(), s.isFleetsCanRunInParallel());
dbSeries.put(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name(), s.hasSplitFleetContiguousScoring());
dbSeries.put(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name(), s.isStartsWithZeroScore());
dbSeries.put(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name(), s.isFirstColumnIsNonDiscardableCarryForward());
@@ -1002,6 +1003,13 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
}
public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogUseCompetitorsFromRaceLogEvent event) {
BasicDBObject result = new BasicDBObject();
storeRaceLogIdentifier(raceLogIdentifier, result);
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogUseCompetitorsFromRaceLogEvent(event));
return result;
}
public DBObject storeRaceLogUseCompetitorsFromRaceLogEvent(RaceLogUseCompetitorsFromRaceLogEvent event) {
DBObject result = new BasicDBObject();
storeRaceLogEventProperties(event, result);
result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogUseCompetitorsFromRaceLogEvent.class.getSimpleName());
@@ -1109,6 +1117,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
storeRaceLogEventProperties(courseDesignChangedEvent, result);
result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCourseDesignChangedEvent.class.getSimpleName());
result.put(FieldNames.RACE_LOG_COURSE_DESIGN_NAME.name(), courseDesignChangedEvent.getCourseDesign().getName());
result.put(FieldNames.RACE_LOG_COURSE_DESIGNER_MODE.name(),
courseDesignChangedEvent.getCourseDesignerMode() == null ? null : courseDesignChangedEvent.getCourseDesignerMode().name());
result.put(FieldNames.RACE_LOG_COURSE_DESIGN.name(), storeCourseBase(courseDesignChangedEvent.getCourseDesign()));
return result;
}
@@ -1,94 +1,94 @@
package com.sap.sailing.domain.racelogtracking.test;
import static com.sap.sse.common.Util.size;
import static junit.framework.Assert.assertEquals;
import java.util.UUID;
import org.junit.After;
import org.junit.Before;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoGPSFixStoreImpl;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
import com.sap.sailing.domain.racelog.tracking.test.mock.MockDeviceAndSessionIdentifierWithGPSFixesDeserializer;
import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory;
import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.gateway.deserialization.impl.DeviceAndSessionIdentifierWithGPSFixesDeserializer;
import com.sap.sailing.server.impl.RacingEventServiceImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class AbstractGPSFixStoreTest {
protected RacingEventService service;
protected final MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory();
DeviceAndSessionIdentifierWithGPSFixesDeserializer deserializer =
new MockDeviceAndSessionIdentifierWithGPSFixesDeserializer();
protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a");
protected RaceLog raceLog;
protected RegattaLog regattaLog;
protected GPSFixStore store;
protected final Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowanceInSecondsPerNauticalMile */ null);
protected final Mark mark = DomainFactory.INSTANCE.getOrCreateMark("mark");
protected final AbstractLogEventAuthor author = new LogEventAuthorImpl("author", 0);
protected GPSFixMoving createFix(long millis, double lat, double lng, double knots, double degrees) {
return new GPSFixMovingImpl(new DegreePosition(lat, lng),
new MillisecondsTimePoint(millis), new KnotSpeedWithBearingImpl(knots, new DegreeBearingImpl(degrees)));
}
@Before
public void setServiceAndRaceLog() {
service = new RacingEventServiceImpl(null, null, serviceFinderFactory);
raceLog = new RaceLogImpl("racelog");
regattaLog = new RegattaLogImpl("regattalog");
store = new MongoGPSFixStoreImpl(service.getMongoObjectFactory(), service.getDomainObjectFactory(),
serviceFinderFactory);
}
@After
public void after() {
MongoObjectFactoryImpl mongoOF = (MongoObjectFactoryImpl) service.getMongoObjectFactory();
mongoOF.getGPSFixCollection().drop();
mongoOF.getGPSFixMetadataCollection().drop();
}
protected void map(RegattaLog regattaLog, Competitor comp, DeviceIdentifier device, long from, long to) {
regattaLog.add(new RegattaLogDeviceCompetitorMappingEventImpl(MillisecondsTimePoint.now(), MillisecondsTimePoint.now(), author, UUID.randomUUID(),
comp, device, new MillisecondsTimePoint(from), new MillisecondsTimePoint(to)));
}
protected void map(Competitor comp, DeviceIdentifier device, long from, long to) {
map(regattaLog, comp, device, from, to);
}
protected void map(Mark mark, DeviceIdentifier device, long from, long to) {
regattaLog.add(new RegattaLogDeviceMarkMappingEventImpl(MillisecondsTimePoint.now(), MillisecondsTimePoint.now(), author, UUID.randomUUID(),
mark, device, new MillisecondsTimePoint(from), new MillisecondsTimePoint(to)));
}
protected void testNumberOfRawFixes(Track<?> track, long expected) {
track.lockForRead();
assertEquals(expected, size(track.getRawFixes()));
track.unlockAfterRead();
}
}
package com.sap.sailing.domain.racelogtracking.test;
import static com.sap.sse.common.Util.size;
import static junit.framework.Assert.assertEquals;
import java.util.UUID;
import org.junit.After;
import org.junit.Before;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoGPSFixStoreImpl;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
import com.sap.sailing.domain.racelog.tracking.test.mock.MockDeviceAndSessionIdentifierWithGPSFixesDeserializer;
import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory;
import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.server.RacingEventService;
import com.sap.sailing.server.gateway.deserialization.impl.DeviceAndSessionIdentifierWithGPSFixesDeserializer;
import com.sap.sailing.server.impl.RacingEventServiceImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class AbstractGPSFixStoreTest {
protected RacingEventService service;
protected final MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory();
DeviceAndSessionIdentifierWithGPSFixesDeserializer deserializer =
new MockDeviceAndSessionIdentifierWithGPSFixesDeserializer();
protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a");
protected RaceLog raceLog;
protected RegattaLog regattaLog;
protected GPSFixStore store;
protected final Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowanceInSecondsPerNauticalMile */ null, null);
protected final Mark mark = DomainFactory.INSTANCE.getOrCreateMark("mark");
protected final AbstractLogEventAuthor author = new LogEventAuthorImpl("author", 0);
protected GPSFixMoving createFix(long millis, double lat, double lng, double knots, double degrees) {
return new GPSFixMovingImpl(new DegreePosition(lat, lng),
new MillisecondsTimePoint(millis), new KnotSpeedWithBearingImpl(knots, new DegreeBearingImpl(degrees)));
}
@Before
public void setServiceAndRaceLog() {
service = new RacingEventServiceImpl(null, null, serviceFinderFactory);
raceLog = new RaceLogImpl("racelog");
regattaLog = new RegattaLogImpl("regattalog");
store = new MongoGPSFixStoreImpl(service.getMongoObjectFactory(), service.getDomainObjectFactory(),
serviceFinderFactory);
}
@After
public void after() {
MongoObjectFactoryImpl mongoOF = (MongoObjectFactoryImpl) service.getMongoObjectFactory();
mongoOF.getGPSFixCollection().drop();
mongoOF.getGPSFixMetadataCollection().drop();
}
protected void map(RegattaLog regattaLog, Competitor comp, DeviceIdentifier device, long from, long to) {
regattaLog.add(new RegattaLogDeviceCompetitorMappingEventImpl(MillisecondsTimePoint.now(), MillisecondsTimePoint.now(), author, UUID.randomUUID(),
comp, device, new MillisecondsTimePoint(from), new MillisecondsTimePoint(to)));
}
protected void map(Competitor comp, DeviceIdentifier device, long from, long to) {
map(regattaLog, comp, device, from, to);
}
protected void map(Mark mark, DeviceIdentifier device, long from, long to) {
regattaLog.add(new RegattaLogDeviceMarkMappingEventImpl(MillisecondsTimePoint.now(), MillisecondsTimePoint.now(), author, UUID.randomUUID(),
mark, device, new MillisecondsTimePoint(from), new MillisecondsTimePoint(to)));
}
protected void testNumberOfRawFixes(Track<?> track, long expected) {
track.lockForRead();
assertEquals(expected, size(track.getRawFixes()));
track.unlockAfterRead();
}
}
@@ -76,7 +76,7 @@ public class CreateAndTrackWithRaceLogTest {
gpsFixStore = service.getGPSFixStore();
service.getMongoObjectFactory().getDatabase().dropDatabase();
author = service.getServerAuthor();
Series series = new SeriesImpl("series", false, Collections.singletonList(fleet), Collections.emptySet(),
Series series = new SeriesImpl("series", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Collections.singletonList(fleet), Collections.emptySet(),
service);
regatta = service.createRegatta(RegattaImpl.getDefaultName("regatta", "Laser"), "Laser",
/* startDate */null, /* endDate */null, UUID.randomUUID(), Collections.<Series> singletonList(series), false,
@@ -170,7 +170,7 @@ public class CreateAndTrackWithRaceLogTest {
// add a mapping and one fix in, one out of mapping
Competitor comp1 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp1", "comp1", null, null, null, null, null,
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
DeviceIdentifier dev1 = new SmartphoneImeiIdentifier("dev1");
regattaLog.add(new RegattaLogDeviceCompetitorMappingEventImpl(t(), t(), author, 0, comp1, dev1, t(0), t(10)));
addFixes0(dev1);
@@ -206,7 +206,7 @@ public class CreateAndTrackWithRaceLogTest {
// add a mapping and one fix in, one out of mapping
Competitor comp1 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp1", "comp1", null, null, null, null, null,
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
DeviceIdentifier dev1 = new SmartphoneImeiIdentifier("dev1");
regattaLog.add(new RegattaLogDeviceCompetitorMappingEventImpl(t(), t(), author, UUID.randomUUID(), comp1, dev1,
t(0), t(10)));
@@ -1,150 +1,150 @@
package com.sap.sailing.domain.racelogtracking.test.impl;
import static junit.framework.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDefineMarkEventImpl;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.impl.TimeRangeImpl;
public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
private final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("49er");
@Test
public void doesRaceLoadOnlyBetweenStartAndEndOfTracking() throws TransformationException,
NoCorrespondingServiceRegisteredException, InterruptedException {
Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null, null,
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2");
DeviceIdentifier markDevice = new SmartphoneImeiIdentifier("imei2");
regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(1), author, new MillisecondsTimePoint(1), 0, mark));
regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(2), author, new MillisecondsTimePoint(1), 0, mark2));
Course course = new CourseImpl("course", Arrays.asList(new Waypoint[] { new WaypointImpl(mark),
new WaypointImpl(mark2) }));
RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(new Competitor[] { comp, comp2 }));
map(comp, device, 0, 10000);
map(mark, markDevice, 0, 10000);
store.storeFix(device, createFix(100, 10, 20, 30, 40)); // before
store.storeFix(device, createFix(1100, 10, 20, 30, 40)); // in
store.storeFix(device, createFix(2100, 10, 20, 30, 40)); // after
store.storeFix(markDevice, createFix(100, 10, 20, 30, 40));
store.storeFix(markDevice, createFix(1100, 10, 20, 30, 40));
store.storeFix(markDevice, createFix(2100, 10, 20, 30, 40));
TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */
null, /* endDate */null, null, null, "a", null));
final DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race,
Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false,
OneDesignRankingMetric::new, mock(RaceLogResolver.class));
trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(1000));
trackedRace.setEndOfTrackingReceived(new MillisecondsTimePoint(2000));
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingFromGPSFixStoreToFinishRunning(regattaLog);
testNumberOfRawFixes(trackedRace.getTrack(comp), 1);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
// now extend the tracking interval of the tracked race and assert that the additional fixes are loaded
trackedRace.setEndOfTrackingReceived(new MillisecondsTimePoint(2500), /* wait for fixes to load */ true);
testNumberOfRawFixes(trackedRace.getTrack(comp), 2);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 2);
trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(0), /* wait for fixes to load */ true);
testNumberOfRawFixes(trackedRace.getTrack(comp), 3);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 3);
}
@Test
public void areFixesStoredInDb() throws TransformationException, NoCorrespondingServiceRegisteredException,
InterruptedException {
Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null, null,
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2");
regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(1), author, new MillisecondsTimePoint(1), 0, mark));
regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(2), author, new MillisecondsTimePoint(1), 0, mark2));
DeviceIdentifier device2 = new SmartphoneImeiIdentifier("imei2");
DeviceIdentifier device3 = new SmartphoneImeiIdentifier("imei3");
Course course = new CourseImpl("course", Arrays.asList(new Waypoint[] { new WaypointImpl(mark),
new WaypointImpl(mark2) }));
RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(new Competitor[] { comp,
comp2 }));
map(comp, device, 0, 20000);
map(comp2, device2, 0, 600);
// reuse device for two marks
map(mark, device3, 0, 600);
map(mark2, device3, 0, 600);
store.storeFix(device, createFix(100, 10, 20, 30, 40));
store.storeFix(device, createFix(200, 10, 20, 30, 40));
store.storeFix(device2, createFix(100, 10, 20, 30, 40));
store.storeFix(device3, createFix(100, 10, 20, 30, 40));
store.storeFix(device3, createFix(100, 10, 20, 30, 40));
for (int i = 0; i < 10000; i++) {
store.storeFix(device, createFix(i + 1000, 10, 20, 30, 40));
}
TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */
null, /* endDate */null, null, null, "a", null));
DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race,
Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false,
OneDesignRankingMetric::new, mock(RaceLogResolver.class));
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingFromGPSFixStoreToFinishRunning(regattaLog);
testNumberOfRawFixes(trackedRace.getTrack(comp), 10002);
testNumberOfRawFixes(trackedRace.getTrack(comp2), 1);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark2), 1);
}
@Test
public void metadataStoredInDb() throws TransformationException, NoCorrespondingServiceRegisteredException {
assertEquals(0, store.getNumberOfFixes(device));
assertEquals(null, store.getTimeRangeCoveredByFixes(device));
map(comp, device, 0, 600);
store.storeFix(device, createFix(100, 10, 20, 30, 40));
store.storeFix(device, createFix(200, 10, 20, 30, 40));
assertEquals(2, store.getNumberOfFixes(device));
assertEquals(TimeRangeImpl.create(100, 200), store.getTimeRangeCoveredByFixes(device));
}
}
package com.sap.sailing.domain.racelogtracking.test.impl;
import static junit.framework.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDefineMarkEventImpl;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.impl.TimeRangeImpl;
public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
private final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("49er");
@Test
public void doesRaceLoadOnlyBetweenStartAndEndOfTracking() throws TransformationException,
NoCorrespondingServiceRegisteredException, InterruptedException {
Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null, null,
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2");
DeviceIdentifier markDevice = new SmartphoneImeiIdentifier("imei2");
regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(1), author, new MillisecondsTimePoint(1), 0, mark));
regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(2), author, new MillisecondsTimePoint(1), 0, mark2));
Course course = new CourseImpl("course", Arrays.asList(new Waypoint[] { new WaypointImpl(mark),
new WaypointImpl(mark2) }));
RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(new Competitor[] { comp, comp2 }));
map(comp, device, 0, 10000);
map(mark, markDevice, 0, 10000);
store.storeFix(device, createFix(100, 10, 20, 30, 40)); // before
store.storeFix(device, createFix(1100, 10, 20, 30, 40)); // in
store.storeFix(device, createFix(2100, 10, 20, 30, 40)); // after
store.storeFix(markDevice, createFix(100, 10, 20, 30, 40));
store.storeFix(markDevice, createFix(1100, 10, 20, 30, 40));
store.storeFix(markDevice, createFix(2100, 10, 20, 30, 40));
TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */
null, /* endDate */null, null, null, "a", null));
final DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race,
Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false,
OneDesignRankingMetric::new, mock(RaceLogResolver.class));
trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(1000));
trackedRace.setEndOfTrackingReceived(new MillisecondsTimePoint(2000));
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingFromGPSFixStoreToFinishRunning(regattaLog);
testNumberOfRawFixes(trackedRace.getTrack(comp), 1);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
// now extend the tracking interval of the tracked race and assert that the additional fixes are loaded
trackedRace.setEndOfTrackingReceived(new MillisecondsTimePoint(2500), /* wait for fixes to load */ true);
testNumberOfRawFixes(trackedRace.getTrack(comp), 2);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 2);
trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(0), /* wait for fixes to load */ true);
testNumberOfRawFixes(trackedRace.getTrack(comp), 3);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 3);
}
@Test
public void areFixesStoredInDb() throws TransformationException, NoCorrespondingServiceRegisteredException,
InterruptedException {
Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null, null,
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2");
regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(1), author, new MillisecondsTimePoint(1), 0, mark));
regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(2), author, new MillisecondsTimePoint(1), 0, mark2));
DeviceIdentifier device2 = new SmartphoneImeiIdentifier("imei2");
DeviceIdentifier device3 = new SmartphoneImeiIdentifier("imei3");
Course course = new CourseImpl("course", Arrays.asList(new Waypoint[] { new WaypointImpl(mark),
new WaypointImpl(mark2) }));
RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(new Competitor[] { comp,
comp2 }));
map(comp, device, 0, 20000);
map(comp2, device2, 0, 600);
// reuse device for two marks
map(mark, device3, 0, 600);
map(mark2, device3, 0, 600);
store.storeFix(device, createFix(100, 10, 20, 30, 40));
store.storeFix(device, createFix(200, 10, 20, 30, 40));
store.storeFix(device2, createFix(100, 10, 20, 30, 40));
store.storeFix(device3, createFix(100, 10, 20, 30, 40));
store.storeFix(device3, createFix(100, 10, 20, 30, 40));
for (int i = 0; i < 10000; i++) {
store.storeFix(device, createFix(i + 1000, 10, 20, 30, 40));
}
TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */
null, /* endDate */null, null, null, "a", null));
DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race,
Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false,
OneDesignRankingMetric::new, mock(RaceLogResolver.class));
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingFromGPSFixStoreToFinishRunning(regattaLog);
testNumberOfRawFixes(trackedRace.getTrack(comp), 10002);
testNumberOfRawFixes(trackedRace.getTrack(comp2), 1);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark), 1);
testNumberOfRawFixes(trackedRace.getOrCreateTrack(mark2), 1);
}
@Test
public void metadataStoredInDb() throws TransformationException, NoCorrespondingServiceRegisteredException {
assertEquals(0, store.getNumberOfFixes(device));
assertEquals(null, store.getTimeRangeCoveredByFixes(device));
map(comp, device, 0, 600);
store.storeFix(device, createFix(100, 10, 20, 30, 40));
store.storeFix(device, createFix(200, 10, 20, 30, 40));
assertEquals(2, store.getNumberOfFixes(device));
assertEquals(TimeRangeImpl.create(100, 200), store.getTimeRangeCoveredByFixes(device));
}
}
@@ -0,0 +1,133 @@
package com.sap.sailing.domain.racelogtracking.test.impl;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import org.junit.Test;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogRaceStatusEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDefineMarkEventImpl;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
private final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("49er");
/**
* tests the precedence order described in {@link TrackedRaceImpl#updateStartAndEndOfTracking()}
*/
@Test
public void testStartTimeInferencePrecedenceOrder() throws TransformationException,
NoCorrespondingServiceRegisteredException, InterruptedException {
Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null, null,
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2");
regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(1), author, new MillisecondsTimePoint(1), 0, mark));
regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(2), author, new MillisecondsTimePoint(1), 0, mark2));
Course course = new CourseImpl("course", Arrays.asList(new Waypoint[] { new WaypointImpl(mark),
new WaypointImpl(mark2) }));
RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(new Competitor[] { comp, comp2 }));
TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE,
EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */
null, /* endDate */null, null, null, "a", null));
final DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race,
Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false,
OneDesignRankingMetric::new, mock(RaceLogResolver.class));
setStartAndEndOfRaceInRaceLog(10000, 20000);
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
//test inference via race start/end time in racelog
trackedRace.waitForLoadingFromGPSFixStoreToFinishRunning(regattaLog);
MillisecondsTimePoint startOfRaceInRaceLog = new MillisecondsTimePoint(10000);
TimePoint expectedStartOfTracking = startOfRaceInRaceLog.minus(Duration.ONE_MINUTE.times(TrackedRaceImpl.TRACKING_BUFFER_IN_MINUTES));
MillisecondsTimePoint endOfRaceInRaceLog = new MillisecondsTimePoint(20000);
TimePoint expectedEndOfTracking = endOfRaceInRaceLog.plus(Duration.ONE_MINUTE.times(TrackedRaceImpl.TRACKING_BUFFER_IN_MINUTES));
assertEquals(expectedStartOfTracking, trackedRace.getStartOfTracking());
assertEquals(expectedEndOfTracking, trackedRace.getEndOfTracking());
//test inference via manually set start/end of tracking
setManualTrackingTimesOnTrackedRace(trackedRace, 30000, 40000);
assertEquals(new MillisecondsTimePoint(30000), trackedRace.getStartOfTracking());
assertEquals(new MillisecondsTimePoint(40000), trackedRace.getEndOfTracking());
//shouldn't change when setting time in raceLog again
setStartAndEndOfRaceInRaceLog(100000, 200000);
assertEquals(new MillisecondsTimePoint(30000), trackedRace.getStartOfTracking());
assertEquals(new MillisecondsTimePoint(40000), trackedRace.getEndOfTracking());
//test inference via start/end of tracking in RaceLog
setStartAndEndOfTrackingInRaceLog(50000, 60000);
MillisecondsTimePoint startOfTrackingInRacelog = new MillisecondsTimePoint(50000);
MillisecondsTimePoint endOfTrackingInRacelog = new MillisecondsTimePoint(60000);
assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking());
assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking());
//shouldn't change when setting time in raceLog again
setStartAndEndOfRaceInRaceLog(300000, 400000);
assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking());
assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking());
//shouldn't change when setting start/end of tracking
setManualTrackingTimesOnTrackedRace(trackedRace, 500000, 600000);
assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking());
assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking());
}
public void setStartAndEndOfTrackingInRaceLog(int startTime, int endTime){
MillisecondsTimePoint startOfTrackingInRacelog = new MillisecondsTimePoint(startTime);
MillisecondsTimePoint endOfTrackingInRacelog = new MillisecondsTimePoint(endTime);
raceLog.add(new RaceLogStartOfTrackingEventImpl(startOfTrackingInRacelog, author, 0));
raceLog.add(new RaceLogEndOfTrackingEventImpl(endOfTrackingInRacelog, author, 0));
}
public void setStartAndEndOfRaceInRaceLog(int startTime, int endTime){
MillisecondsTimePoint startTimeInRaceLog2 = new MillisecondsTimePoint(startTime);
MillisecondsTimePoint endTimeInRaceLog2 = new MillisecondsTimePoint(endTime);
raceLog.add(new RaceLogStartTimeEventImpl(startTimeInRaceLog2, author, 0, startTimeInRaceLog2));
raceLog.add(new RaceLogRaceStatusEventImpl(endTimeInRaceLog2, endTimeInRaceLog2, author, UUID.randomUUID(), 0, RaceLogRaceStatus.FINISHED));
}
public void setManualTrackingTimesOnTrackedRace(DynamicTrackedRace trackedRace, int startTime, int endTime){
trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(startTime));
trackedRace.setEndOfTrackingReceived(new MillisecondsTimePoint(endTime));
}
}
@@ -1,2 +0,0 @@
eclipse.preferences.version=1
encoding//resources/stringmessages/TrackingStringMessages_de.properties=UTF-8
@@ -21,6 +21,7 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sse.mail
Import-Package: com.google.zxing;version="3.1.0",
com.mongodb,
com.sap.sse.shared.media,
javax.mail;version="1.4.0",
javax.mail.internet;version="1.4.0",
javax.mail.util;version="1.4.0",
@@ -1,7 +1,12 @@
trackingInvitationFor=Einladung zum Tracken für
scanQRCodeOrVisitUrlToRegisterAs=Installiere die SAP Sailing Boat Tracker App. Dann scanne den QRCode oder klicke den Link mit deinem Smartphone an, um dich zu registrieren als
trackingInvitationFor=Einladung zum Tracken für
scanQRCodeOrVisitUrlToRegisterAs=Installiere die {0} App. Dann scanne den QRCode oder klicke den Link mit deinem Smartphone an, um dich zu registrieren als
welcomeTo=Willkommen zu
iOSUsers=iOS-Benutzer
androidUsers=Android-Benutzer
alternativelyVisitThisLink=Alternativ kannst du, wenn du die App bereits installiert hast, diesen Link anklicken
buoyTender=Bojenleger
iOSUsers=iOS App starten
androidUsers=Android App starten
alternativelyVisitThisLink=Wenn die App bereits installiert ist, und du diese Mail auch auf dem Geräte offen hast, dann einfach hier clicken:
buoyTender=Bojenleger
appStoreInstallText=Falls die App noch nicht installiert ist, kannst du sie hier finden:Ÿ
appIos=iOS App installieren
appAndroid=Android App installieren
buoyPingerAppName=SAP Sailing Buoy Pinger
sailInSightAppName=SAP Sail InSight
@@ -1,7 +1,12 @@
trackingInvitationFor=Tracking Invitation for
scanQRCodeOrVisitUrlToRegisterAs=Make sure you have the SAP Sailing Boat Tracker App installed. Then scan the following QRCode or click the link using your smartphone to register as
scanQRCodeOrVisitUrlToRegisterAs=Make sure you have the {0} App installed. Then scan the following QRCode or click the link using your smartphone to register as
welcomeTo=Welcome to
iOSUsers=iOS Users
androidUsers=Android Users
alternativelyVisitThisLink=Alternatively, when you have the app already installed, you can click this Link
buoyTender=buoy tender
iOSUsers=Start iOS App
androidUsers=Start Android App
alternativelyVisitThisLink=If you are reading this mail on the device with the App already installed, simply click below to start:
buoyTender=buoy tender
appStoreInstallText=If don't have the App yet, please install:
appIos=Install iOS App
appAndroid=Install Android App
buoyPingerAppName=SAP Sailing Buoy Pinger
sailInSightAppName=SAP Sail InSight
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
import com.sap.sailing.domain.racelogtracking.impl.RaceLogRaceTracker;
import com.sap.sailing.domain.tracking.RaceHandle;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -87,15 +88,17 @@ public interface RaceLogTrackingAdapter {
* @throws MailException
*/
void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard,
String serverUrlWithoutTrailingSlash, Set<Competitor> competitors, Locale locale) throws MailException;
String serverUrlWithoutTrailingSlash, Set<Competitor> competitors, String iOSAppUrl, String androidAppUrl,
Locale locale) throws MailException;
/**
* Invite buoy tenders for buoy pinging via the Buoy Tender App by sending out emails.
*
* @param appUrl
* @throws MailException
*/
void inviteBuoyTenderViaEmail(Event event, Leaderboard leaderboard, String serverUrlWithoutTrailingSlash,
String emails, Locale locale) throws MailException;
String emails, String iOSAppUrl, String androidAppUrl, Locale locale) throws MailException;
/**
* Copy the course in the newest {@link RaceLogCourseDesignChangedEvent} in {@code from} race log to the {@code to}
@@ -19,11 +19,14 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastPublishedCourseDesignFinder;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TrackingTimesFinder;
import com.sap.sailing.domain.abstractlog.race.impl.BaseRaceLogEventVisitor;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEvent;
@@ -56,6 +59,8 @@ import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogException;
import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState;
import com.sap.sailing.domain.common.racelog.tracking.RaceNotCreatedException;
@@ -145,6 +150,18 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
public void visit(RaceLogEndOfTrackingEvent event) {
RaceLogRaceTracker.this.onEndOfTrackingEvent(event);
}
@Override
public void visit(RaceLogStartTimeEvent event) {
trackedRace.updateStartAndEndOfTracking();
}
@Override
public void visit(RaceLogRaceStatusEvent event) {
if (event.getNextStatus().equals(RaceLogRaceStatus.FINISHED)){
trackedRace.updateStartAndEndOfTracking();
}
}
};
visitors.put(log, visitor);
((RaceLog) log).addListener(visitor);
@@ -181,6 +198,14 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
@Override
public void stop(boolean preemptive) {
RaceLog raceLog = params.getRaceLog();
final Pair<TimePointSpecificationFoundInLog, TimePointSpecificationFoundInLog> trackingTimes = new TrackingTimesFinder(raceLog).analyze();
if (trackingTimes == null || trackingTimes.getB() == null || trackingTimes.getB().getTimePoint() == null) {
// seems the first time tracking for this race is stopped; enter "now" as end of tracking
// into the race log
raceLog.add(new RaceLogEndOfTrackingEventImpl(MillisecondsTimePoint.now(), raceLogEventAuthor, /* passId */ 0));
}
// mark passing calculator is automatically stopped, when the race status is set to {@link
// TrackedRaceStatusEnum#FINISHED}
trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, 100));
@@ -399,19 +424,19 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
private void onStartOfTrackingEvent(RaceLogStartOfTrackingEvent event) {
if (trackedRace != null) {
trackedRace.setStartOfTrackingReceived(event.getLogicalTimePoint());
trackedRace.updateStartAndEndOfTracking();
}
}
private void onEndOfTrackingEvent(RaceLogEndOfTrackingEvent event) {
if (trackedRace != null) {
trackedRace.setEndOfTrackingReceived(event.getLogicalTimePoint());
trackedRace.updateStartAndEndOfTracking();
}
}
private void onCourseDesignChangedEvent(RaceLogCourseDesignChangedEvent event) {
if (trackedRace != null) {
CourseBase base = new LastPublishedCourseDesignFinder(params.getRaceLog()).analyze();
CourseBase base = new LastPublishedCourseDesignFinder(params.getRaceLog(), /* onlyCoursesWithValidWaypointList */ true).analyze();
List<Util.Pair<ControlPoint, PassingInstruction>> update = new ArrayList<>();
for (Waypoint waypoint : base.getWaypoints()) {
update.add(new Util.Pair<>(waypoint.getControlPoint(), waypoint.getPassingInstructions()));
@@ -429,15 +454,15 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
RaceColumn raceColumn = params.getRaceColumn();
Fleet fleet = params.getFleet();
RaceLogDenoteForTrackingEvent denoteEvent = new RaceInformationFinder(raceLog).analyze();
final Pair<TimePoint, TimePoint> trackingTimes = new TrackingTimesFinder(raceLog).analyze();
if (trackingTimes == null) {
// seems the first time tracking for this race is started; enter "now" as start of tracking
final Pair<TimePointSpecificationFoundInLog, TimePointSpecificationFoundInLog> trackingTimes = new TrackingTimesFinder(raceLog).analyze();
if (trackingTimes == null || trackingTimes.getA() == null || trackingTimes.getA().getTimePoint() == null) {
// the start of tracking interval is unset or set to null; enter "now" as start of tracking
// into the race log
raceLog.add(new RaceLogStartOfTrackingEventImpl(MillisecondsTimePoint.now(), raceLogEventAuthor, /* passId */ 0));
}
BoatClass boatClass = denoteEvent.getBoatClass();
String raceName = denoteEvent.getRaceName();
CourseBase courseBase = new LastPublishedCourseDesignFinder(raceLog).analyze();
CourseBase courseBase = new LastPublishedCourseDesignFinder(raceLog, /* onlyCoursesWithValidWaypointList */ true).analyze();
if (courseBase == null) {
courseBase = new CourseDataImpl("Default course for " + raceName);
logger.log(Level.FINE, "Using empty course in creation of race " + raceName);
@@ -507,7 +532,22 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
for (DeviceMapping<Mark> mapping : markMappingsByDevices.get(device)) {
Mark mark = mapping.getMappedTo();
if (mapping.getTimeRange().includes(timePoint)) {
trackedRace.recordFix(mark, fix);
final DynamicGPSFixTrack<Mark, GPSFix> markTrack = trackedRace.getOrCreateTrack(mark);
final GPSFix firstFixAtOrAfter;
boolean forceFix;
markTrack.lockForRead();
try {
forceFix = Util.isEmpty(markTrack.getRawFixes()) ||
(firstFixAtOrAfter=markTrack.getFirstFixAtOrAfter(timePoint)) != null &&
firstFixAtOrAfter.getTimePoint().equals(timePoint);
} finally {
markTrack.unlockAfterRead();
}
if (forceFix) {
trackedRace.recordFix(mark, fix, /* only when in tracking interval */ false); // force fix into track
} else {
trackedRace.recordFix(mark, fix);
}
}
}
}
@@ -3,6 +3,7 @@ package com.sap.sailing.domain.racelogtracking.impl;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
@@ -46,6 +47,7 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.SharedDomainFactory;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.CourseDataImpl;
import com.sap.sailing.domain.common.CourseDesignerMode;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sailing.domain.common.racelog.tracking.CompetitorRegistrationOnRaceLogDisabledException;
@@ -69,8 +71,11 @@ import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.mail.MailException;
import com.sap.sse.common.media.MediaTagConstants;
import com.sap.sse.i18n.ResourceBundleStringMessages;
import com.sap.sse.mail.MailService;
import com.sap.sse.qrcode.QRCodeGenerationUtil;
import com.sap.sse.shared.media.ImageDescriptor;
import com.sap.sse.util.impl.NonGwtUrlHelper;
public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
@@ -190,7 +195,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
@Override
public void copyCourse(RaceLog fromRaceLog, Set<RaceLog> toRaceLogs, SharedDomainFactory baseDomainFactory,
RacingEventService service) {
CourseBase course = new LastPublishedCourseDesignFinder(fromRaceLog).analyze();
CourseBase course = new LastPublishedCourseDesignFinder(fromRaceLog, /* onlyCoursesWithValidWaypointList */ true).analyze();
final Set<Mark> marks = new HashSet<>();
if (course != null) {
course.getWaypoints().forEach(wp -> Util.addAll(wp.getMarks(), marks));
@@ -199,7 +204,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
for (RaceLog toRaceLog : toRaceLogs) {
if (new RaceLogTrackingStateAnalyzer(toRaceLog).analyze().isForTracking()) {
if (course != null) {
CourseBase newCourse = new CourseDataImpl("Copy of \"" + course.getName() + "\"");
CourseBase newCourse = new CourseDataImpl(course.getName());
TimePoint now = MillisecondsTimePoint.now();
int i = 0;
for (Waypoint oldWaypoint : course.getWaypoints()) {
@@ -208,7 +213,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
int passId = toRaceLog.getCurrentPassId();
RaceLogEvent newCourseEvent = new RaceLogCourseDesignChangedEventImpl(now,
service.getServerAuthor(), passId, newCourse);
service.getServerAuthor(), passId, newCourse, CourseDesignerMode.ADMIN_CONSOLE);
toRaceLog.add(newCourseEvent);
}
@@ -277,20 +282,26 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
@Override
public void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard,
String serverUrlWithoutTrailingSlash, Set<Competitor> competitors, Locale locale) throws MailException {
String serverUrlWithoutTrailingSlash, Set<Competitor> competitors, String iOSAppUrl, String androidAppUrl,
Locale locale) throws MailException {
StringBuilder occuredExceptions = new StringBuilder();
for (Competitor competitor : competitors) {
final String toAddress = competitor.getEmail();
if (toAddress != null) {
String leaderboardName = leaderboard.getName();
String competitorName = competitor.getName();
String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash,
event.getId().toString(), leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING,
competitor.getId().toString(), NonGwtUrlHelper.INSTANCE);
String logoUrl = null;
final List<ImageDescriptor> imagesWithTag = event.findImagesWithTag(MediaTagConstants.LOGO);
if (imagesWithTag != null && !imagesWithTag.isEmpty()) {
logoUrl = imagesWithTag.get(0).getURL().toString();
}
try {
sendInvitationEmail(locale, toAddress, leaderboardName, competitorName, url);
final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES;
sendInvitationEmail(locale, toAddress, leaderboardName, competitorName, url, B.get(locale, "sailInSightAppName"),
iOSAppUrl, androidAppUrl, logoUrl);
} catch (MailException e) {
occuredExceptions.append(e.getMessage() + "\r\n");
}
@@ -302,29 +313,69 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
}
private void sendInvitationEmail(Locale locale, final String toAddress, String leaderboardName, String invitee,
String url) throws MailException {
String subject = String.format("%s %s",
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "trackingInvitationFor"), invitee);
String url, String appName, String iOSAppUrl, String androidAppUrl, String logoUrl) throws MailException {
final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES;
String subject = String.format("%s %s", B.get(locale, "trackingInvitationFor"), invitee);
// taken from http://www.tutorialspoint.com/javamail_api/javamail_api_send_inlineimage_in_email.htm
BodyPart messageTextPart = new MimeBodyPart();
String htmlText = String.format("<h1>%s %s</h1>" + "<p>%s <b>%s</b></p>"
+ "<img src=\"cid:image\" title=\"%s\"><br/><b>%s</b>: <a href=\"%s\">%s</a><br/><b>%s</b>: <a href=\"%s\">%s</a>",
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "welcomeTo"), leaderboardName,
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "scanQRCodeOrVisitUrlToRegisterAs"), invitee,
url, RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "iOSUsers"),
IOS_DEEP_LINK_PREFIX+url, RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "alternativelyVisitThisLink"),
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "androidUsers"),
url, RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "alternativelyVisitThisLink"));
boolean hasIOSAppUrl = iOSAppUrl != null && !iOSAppUrl.isEmpty();
boolean hasAndroidAppUrl = androidAppUrl != null && !androidAppUrl.isEmpty();
boolean hasLogoUrl = logoUrl != null && !logoUrl.isEmpty();
StringBuilder htmlText = new StringBuilder();
htmlText.append("<!doctype html>\n");
htmlText.append("<html>");
htmlText.append("<head><title>");
htmlText.append(B.get(locale, "welcomeTo")).append(" ").append(leaderboardName).append("</title>");
htmlText.append("<style type=\"text/css\">\n");
htmlText.append(".b,.b:active,b:visited {padding:15px;margin:10px;width:200px;display:inline-block;background-color:#337ab7;border-radius:4px;color:#ffffff;border:1px solid #2e6da4;text-decoration:none;}\n");
htmlText.append(".b:hover {background-color:#2b618e;border:1px solid #204d74;}\n");
htmlText.append(".qr {margin: 10px;height:250px; width: auto;}\n");
htmlText.append(".spacer {margin-top: 50px;}\n");
htmlText.append("</style>");
htmlText.append("</head>");
htmlText.append("<body>");
if (hasLogoUrl) {
htmlText.append("<p><img src=\"").append(logoUrl).append("\" /></p> ");
}
htmlText.append("<h1>").append(B.get(locale, "welcomeTo")).append(" ").append(leaderboardName).append("</h1> ");
htmlText.append("<p>").append(B.get(locale, "scanQRCodeOrVisitUrlToRegisterAs", appName)).append(" <b>").append(invitee)
.append("</b></p> ");
htmlText.append("<p class=\"qr\"><img src=\"cid:image\" title=\"").append(url).append("\" /></p> ");
htmlText.append("<p class=\"spacer\">").append(B.get(locale, "alternativelyVisitThisLink")).append("</p> ");
if (hasIOSAppUrl) {
htmlText.append("<a class=\"b\" href=\"").append(IOS_DEEP_LINK_PREFIX + url).append("\">")
.append(B.get(locale, "iOSUsers")).append("</a> ");
}
if (hasAndroidAppUrl) {
htmlText.append("<a class=\"b\" href=\"").append(url).append("\">").append(B.get(locale, "androidUsers"))
.append("</a> ");
}
if (hasIOSAppUrl || hasAndroidAppUrl) {
htmlText.append("<p class=\"spacer\">").append(B.get(locale, "appStoreInstallText")).append("</p> ");
}
if (hasIOSAppUrl) {
htmlText.append("<a class=\"b\" href=\"").append(iOSAppUrl).append("\">").append(B.get(locale, "appIos"))
.append("</a> ");
}
if (hasAndroidAppUrl) {
htmlText.append("<a class=\"b\" href=\"").append(androidAppUrl).append("\">")
.append(B.get(locale, "appAndroid")).append("</a> ");
}
htmlText.append("<p class=\"spacer\"></p>");
htmlText.append("</body>");
htmlText.append("</html>");
try {
messageTextPart.setContent(htmlText, "text/html");
messageTextPart.setContent(htmlText.toString(), "text/html");
BodyPart messageImagePart = new MimeBodyPart();
InputStream imageIs = QRCodeGenerationUtil.create(url, 250);
DataSource imageDs = new ByteArrayDataSource(imageIs, "image/png");
messageImagePart.setDataHandler(new DataHandler(imageDs));
messageImagePart.setHeader("Content-ID", "<image>");
messageImagePart.setHeader("Content-Disposition", "inline;filename=\"qr.png\"");
MimeMultipart multipart = new MimeMultipart();
multipart.addBodyPart(messageTextPart);
@@ -339,27 +390,29 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
@Override
public void inviteBuoyTenderViaEmail(Event event, Leaderboard leaderboard, String serverUrlWithoutTrailingSlash,
String emails, Locale locale) throws MailException {
String emails, String iOSAppUrl, String androidAppUrl, Locale locale) throws MailException {
StringBuilder occuredExceptions = new StringBuilder();
String[] emailArray = emails.split(",");
String leaderboardName = leaderboard.getName();
String eventId = event.getId().toString();
String logoUrl = null;
List<ImageDescriptor> imagesWithTag = event.findImagesWithTag(MediaTagConstants.LOGO);
if (imagesWithTag != null && !imagesWithTag.isEmpty()) {
logoUrl = imagesWithTag.get(0).getURL().toString();
}
// http://<host>/buoy-tender/checkin?event_id=<event-id>&leaderboard_name=<leaderboard-name>
String url = DeviceMappingConstants.getBuoyTenderInvitationUrl(serverUrlWithoutTrailingSlash, leaderboardName,
eventId, NonGwtUrlHelper.INSTANCE);
for (String toAddress : emailArray) {
try {
final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES;
sendInvitationEmail(locale, toAddress, leaderboardName,
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "buoyTender"), url);
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "buoyTender"), url, B.get(locale, "buoyPingerAppName"), iOSAppUrl,
androidAppUrl, logoUrl);
} catch (MailException e) {
occuredExceptions.append(e.getMessage() + "\r\n");
}
}
if (!(occuredExceptions.length() == 0)) {
throw new MailException(occuredExceptions.toString());
}
@@ -0,0 +1,421 @@
package com.sap.sailing.domain.base.racegroup.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.base.racegroup.CurrentRaceFilter;
import com.sap.sailing.domain.base.racegroup.RaceCell;
import com.sap.sailing.domain.base.racegroup.RaceGroup;
import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleetRaceColumn;
import com.sap.sailing.domain.base.racegroup.RaceRow;
import com.sap.sailing.domain.base.racegroup.SeriesWithRows;
import com.sap.sailing.domain.common.BoatClassMasterdata;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
public class CurrentRaceFilterImplTest {
private CurrentRaceFilter<SimpleFilterableRace> fixture;
private RaceGroup _505;
private RaceGroup yes;
private RaceGroup isaf;
private RaceGroup league;
private RaceGroup ess;
private Map<RaceGroupSeriesFleetRaceColumn, SimpleFilterableRace> races;
@Before
public void setUp() {
races = new HashMap<>();
// five regattas ("RaceGroup"s):
// - simple single-series, single fleet set-up (505 style)
// - single-series, two fleets (Yellow / Blue, as used by YES)
// - qualification with Yellow / Blue; final with Gold / Silver; Medal
// - league set-up with single series with three fleets Red/Green/Blue
_505 = create505Regatta();
races.putAll(getAllRaces(_505));
yes = createYesRegatta();
races.putAll(getAllRaces(yes));
isaf = createIsafRegatta();
races.putAll(getAllRaces(isaf));
league = createLeagueRegatta();
races.putAll(getAllRaces(league));
ess = createEssRegatta();
races.putAll(getAllRaces(ess));
fixture = new CurrentRaceFilterImpl<>(races.values());
}
private RaceGroup create505Regatta() {
final List<SeriesWithRows> _505Series = new ArrayList<>();
final List<RaceRow> _505RaceRows = new ArrayList<>();
final RaceRow _505RaceRow = createRaceRow(/* firstRaceColumnNumber */ 1, /* numberOfRaces */ 9, /* raceColumnNamePrefix */ "R", /* fleetName */ "Default");
_505RaceRows.add(_505RaceRow);
_505Series.add(new SeriesWithRowsImpl("Default", /* isMedal */ false, /* isFleetsCanRunInParallel */ false, _505RaceRows));
final RaceGroup _505 = new RaceGroupImpl("505", /* displayName */ null,
new BoatClassImpl(BoatClassMasterdata._5O5.getDisplayName(), BoatClassMasterdata._5O5),
/* courseArea */ null, _505Series, /* regattaConfiguration */ null);
return _505;
}
private RaceGroup createYesRegatta() {
final List<SeriesWithRows> yesSeries = new ArrayList<>();
final List<RaceRow> yesRaceRows = Arrays.asList(createRaceRow(1, 9, "R", "Yellow"), createRaceRow(1, 9, "R", "Blue"));
yesSeries.add(new SeriesWithRowsImpl("Default", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, yesRaceRows));
final RaceGroup yes = new RaceGroupImpl("YES", /* displayName */ null,
new BoatClassImpl(BoatClassMasterdata._470.getDisplayName(), BoatClassMasterdata._470),
/* courseArea */ null, yesSeries, /* regattaConfiguration */ null);
return yes;
}
private RaceGroup createIsafRegatta() {
final List<SeriesWithRows> isafSeries = new ArrayList<>();
final List<RaceRow> isafQualificationRaceRows = Arrays.asList(createRaceRow(1, 5, "Q", "Yellow"), createRaceRow(1, 5, "Q", "Blue"));
isafSeries.add(new SeriesWithRowsImpl("Qualification", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, isafQualificationRaceRows));
final List<RaceRow> isafFinalRaceRows = Arrays.asList(createRaceRow(6, 5, "F", "Gold"), createRaceRow(6, 5, "F", "Silver"));
isafSeries.add(new SeriesWithRowsImpl("Final", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, isafFinalRaceRows));
final List<RaceRow> isafMedalRaceRows = Arrays.asList(createRaceRow(1, 1, "M", "Medal"));
isafSeries.add(new SeriesWithRowsImpl("Medal", /* isMedal */ true, /* isFleetsCanRunInParallel */ true, isafMedalRaceRows));
final RaceGroup isaf = new RaceGroupImpl("ISAF", /* displayName */ null,
new BoatClassImpl(BoatClassMasterdata._470.getDisplayName(), BoatClassMasterdata._470),
/* courseArea */ null, isafSeries, /* regattaConfiguration */ null);
return isaf;
}
private RaceGroup createEssRegatta() {
final List<SeriesWithRows> essSeries = new ArrayList<>();
final List<RaceRow> essDay1RaceRows = Arrays.asList(createRaceRow(1, 5, "R", "Default"));
essSeries.add(new SeriesWithRowsImpl("Day 1", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, essDay1RaceRows));
final List<RaceRow> essDay2RaceRows = Arrays.asList(createRaceRow(6, 5, "R", "Default"));
essSeries.add(new SeriesWithRowsImpl("Day 2", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, essDay2RaceRows));
final List<RaceRow> essKnockoutQualificationRaceRows = Arrays.asList(createRaceRow(1, 1, "Q", "Yellow"), createRaceRow(1, 1, "Q", "Blue"));
essSeries.add(new SeriesWithRowsImpl("Knockout Qualification", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, essKnockoutQualificationRaceRows));
final List<RaceRow> essKnockoutScoringRaceRows = Arrays.asList(createRaceRow(1, 1, "F", "Gold"), createRaceRow(1, 1, "F", "Silver"));
essSeries.add(new SeriesWithRowsImpl("Knockout Finals", /* isMedal */ false, /* isFleetsCanRunInParallel */ false, essKnockoutScoringRaceRows));
final List<RaceRow> essDay3RaceRows = Arrays.asList(createRaceRow(11, 5, "R", "Default"));
essSeries.add(new SeriesWithRowsImpl("Day 3", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, essDay3RaceRows));
final List<RaceRow> essDay4RaceRows = Arrays.asList(createRaceRow(16, 5, "R", "Default"));
essSeries.add(new SeriesWithRowsImpl("Day 4", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, essDay4RaceRows));
final RaceGroup ess = new RaceGroupImpl("ESS", /* displayName */ null,
new BoatClassImpl(BoatClassMasterdata.EXTREME_40.getDisplayName(), BoatClassMasterdata.EXTREME_40),
/* courseArea */ null, essSeries, /* regattaConfiguration */ null);
return ess;
}
private RaceGroup createLeagueRegatta() {
final List<SeriesWithRows> leagueSeries = new ArrayList<>();
final List<RaceRow> leagueRaceRows = Arrays.asList(createRaceRow(1, 15, "F", "Red"), createRaceRow(1, 15, "F", "Green"), createRaceRow(1, 15, "F", "Blue"));
leagueSeries.add(new SeriesWithRowsImpl("Default", /* isMedal */ false, /* isFleetsCanRunInParallel */ false, leagueRaceRows));
final RaceGroup league = new RaceGroupImpl("League", /* displayName */ null,
new BoatClassImpl(BoatClassMasterdata.J70.getDisplayName(), BoatClassMasterdata.J70),
/* courseArea */ null, leagueSeries, /* regattaConfiguration */ null);
return league;
}
private Map<RaceGroupSeriesFleetRaceColumn, SimpleFilterableRace> getAllRaces(RaceGroup raceGroup) {
final Map<RaceGroupSeriesFleetRaceColumn, SimpleFilterableRace> result = new HashMap<>();
int zeroBasedSeriesIndex = 0;
for (SeriesWithRows series : raceGroup.getSeries()) {
for (final RaceRow raceRow : series.getRaceRows()) {
int zeroBasedIndexInFleet = 0;
for (final RaceCell raceCell : raceRow.getCells()) {
final SimpleFilterableRace race = new SimpleFilterableRace(raceGroup, series, raceRow.getFleet(), zeroBasedIndexInFleet, zeroBasedSeriesIndex, raceCell.getName());
result.put(new RaceGroupSeriesFleetRaceColumn(race), race);
zeroBasedIndexInFleet++;
}
}
zeroBasedSeriesIndex++;
}
return result;
}
@Test
public void testRaceAccessAndStatusUpdate() {
SimpleFilterableRace isafQ4Yellow = get(isaf, "F7", "Gold");
assertNotNull(isafQ4Yellow);
assertEquals("F7", isafQ4Yellow.getRaceColumnName());
assertEquals("Gold", isafQ4Yellow.getFleet().getName());
assertEquals(1, isafQ4Yellow.getZeroBasedIndexInFleet());
assertEquals(1, isafQ4Yellow.getZeroBasedSeriesIndex());
assertEquals(RaceLogRaceStatus.UNSCHEDULED, isafQ4Yellow.getStatus());
isafQ4Yellow.setStatus(RaceLogRaceStatus.SCHEDULED);
assertEquals(RaceLogRaceStatus.SCHEDULED, isafQ4Yellow.getStatus());
}
@Test
public void testBasicFilteringForFirstUnscheduledRaces() {
// with all races unscheduled we can expect the first race of each regatta's first series to show
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
{
final Set<SimpleFilterableRace> current505Races = currentRaces.stream().filter(r->r.getRaceGroup() == _505).collect(Collectors.toSet());
assertEquals(1, current505Races.size());
final SimpleFilterableRace firstRace = current505Races.iterator().next();
assertEquals("R1", firstRace.getRaceColumnName());
}
{
final Set<SimpleFilterableRace> currentYesRaces = currentRaces.stream().filter(r->r.getRaceGroup() == yes).collect(Collectors.toSet());
assertEquals(2, currentYesRaces.size());
for (final SimpleFilterableRace currentYesRace : currentYesRaces) {
assertEquals("R1", currentYesRace.getRaceColumnName());
}
}
{
final Set<SimpleFilterableRace> currentIsafRaces = currentRaces.stream().filter(r->r.getRaceGroup() == isaf).collect(Collectors.toSet());
assertEquals(2, currentIsafRaces.size());
for (final SimpleFilterableRace currentYesRace : currentIsafRaces) {
assertEquals("Q1", currentYesRace.getRaceColumnName());
}
}
{
final Set<SimpleFilterableRace> currentLeagueRaces = currentRaces.stream().filter(r->r.getRaceGroup() == league).collect(Collectors.toSet());
assertEquals(1, currentLeagueRaces.size());
final SimpleFilterableRace firstRace = currentLeagueRaces.iterator().next();
assertEquals("Red", firstRace.getFleet().getName());
assertEquals("F1", firstRace.getRaceColumnName());
}
{
final Set<SimpleFilterableRace> currentEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet());
assertEquals(1, currentEssRaces.size());
final SimpleFilterableRace firstRace = currentEssRaces.iterator().next();
assertEquals("Default", firstRace.getFleet().getName());
assertEquals("R1", firstRace.getRaceColumnName());
}
}
@Test
public void testSchedulingFirst505Race() {
// with all races unscheduled we can expect the first race of each regatta's first series to show
get(_505, "R1", "Default").setStatus(RaceLogRaceStatus.SCHEDULED);
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> current505Races = currentRaces.stream().filter(r->r.getRaceGroup() == _505).collect(Collectors.toSet());
assertEquals(2, current505Races.size());
assertTrue(current505Races.contains(get(_505, "R1", "Default"))); // it's scheduled, so it shows
assertTrue(current505Races.contains(get(_505, "R2", "Default"))); // it's unscheduled and has a scheduled immediate predecessor
}
@Test
public void testSchedulingFirstIsafRace() {
// with all races unscheduled we can expect the first race of each regatta's first series to show
get(isaf, "Q1", "Yellow").setStatus(RaceLogRaceStatus.SCHEDULED);
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> currentIsafRaces = currentRaces.stream().filter(r->r.getRaceGroup() == isaf).collect(Collectors.toSet());
assertEquals(3, currentIsafRaces.size());
assertTrue(currentIsafRaces.contains(get(isaf, "Q1", "Yellow"))); // it's scheduled, so it shows
assertTrue(currentIsafRaces.contains(get(isaf, "Q1", "Blue"))); // it's unscheduled, first in fleet
assertTrue(currentIsafRaces.contains(get(isaf, "Q2", "Yellow"))); // predecessor Q1/Yellow is scheduled, so show this as next in fleet
}
@Test
public void testSchedulingFirstEssRace() {
// with all races unscheduled we can expect the first race of each regatta's first series to show
get(ess, "R1", "Default").setStatus(RaceLogRaceStatus.SCHEDULED);
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> currentEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet());
assertEquals(2, currentEssRaces.size());
assertTrue(currentEssRaces.contains(get(ess, "R1", "Default"))); // it's scheduled, so it shows
assertTrue(currentEssRaces.contains(get(ess, "R2", "Default"))); // it's unscheduled, first after last scheduled
}
@Test
public void testFinishingAllEssRacesBeforeKnockoutQualification() {
for (int i=1; i<=10; i++) {
get(ess, "R"+i, "Default").setStatus(RaceLogRaceStatus.FINISHED);
}
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> currentEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet());
assertEquals(2, currentEssRaces.size());
assertTrue(currentEssRaces.contains(get(ess, "Q1", "Yellow")));
assertTrue(currentEssRaces.contains(get(ess, "Q1", "Blue")));
}
@Test
public void testFinishingAllEssRacesBeforeKnockoutQualificationAndSchedulingFirstKnockout() {
for (int i=1; i<=10; i++) {
get(ess, "R"+i, "Default").setStatus(RaceLogRaceStatus.FINISHED);
}
get(ess, "Q1", "Yellow").setStatus(RaceLogRaceStatus.SCHEDULED);
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> currentEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet());
assertEquals(3, currentEssRaces.size());
assertTrue(currentEssRaces.contains(get(ess, "Q1", "Yellow"))); // scheduled
assertTrue(currentEssRaces.contains(get(ess, "Q1", "Blue"))); // unscheduled
assertTrue(currentEssRaces.contains(get(ess, "F1", "Gold"))); // first unscheduled in series; last column in preceding series has scheduled race
// F1/Silver remains invisible because the knockout finals runs its fleets in sequence
}
@Test
public void testFinishingAllEssRacesBeforeKnockoutFinals() {
for (int i=1; i<=10; i++) {
get(ess, "R"+i, "Default").setStatus(RaceLogRaceStatus.FINISHED);
}
get(ess, "Q1", "Yellow").setStatus(RaceLogRaceStatus.FINISHED);
get(ess, "Q1", "Blue").setStatus(RaceLogRaceStatus.FINISHED);
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> currentEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet());
assertEquals(1, currentEssRaces.size());
assertTrue(currentEssRaces.contains(get(ess, "F1", "Gold"))); // first unscheduled in series; last column in preceding series has scheduled race
// F1/Silver remains invisible because the knockout finals runs its fleets in sequence
}
@Test
public void testFinishingAllEssRacesBeforeKnockoutFinalsAndGoldRaceStarted() {
for (int i=1; i<=10; i++) {
get(ess, "R"+i, "Default").setStatus(RaceLogRaceStatus.FINISHED);
}
get(ess, "Q1", "Yellow").setStatus(RaceLogRaceStatus.FINISHED);
get(ess, "Q1", "Blue").setStatus(RaceLogRaceStatus.FINISHED);
get(ess, "F1", "Gold").setStatus(RaceLogRaceStatus.RUNNING);
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> currentEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet());
assertEquals(2, currentEssRaces.size());
assertTrue(currentEssRaces.contains(get(ess, "F1", "Gold"))); // running
assertTrue(currentEssRaces.contains(get(ess, "F1", "Silver"))); // first unscheduled with a predecessor that's running
}
@Test
public void testFinishingAllEssRacesBeforeKnockoutFinalsAndGoldRaceFinishedAndSilverRaceScheduled() {
for (int i=1; i<=10; i++) {
get(ess, "R"+i, "Default").setStatus(RaceLogRaceStatus.FINISHED);
}
get(ess, "Q1", "Yellow").setStatus(RaceLogRaceStatus.FINISHED);
get(ess, "Q1", "Blue").setStatus(RaceLogRaceStatus.FINISHED);
get(ess, "F1", "Gold").setStatus(RaceLogRaceStatus.FINISHED);
get(ess, "F1", "Silver").setStatus(RaceLogRaceStatus.SCHEDULED);
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> currentEssRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet());
assertEquals(2, currentEssRaces.size());
assertTrue(currentEssRaces.contains(get(ess, "F1", "Silver"))); // scheduled
assertTrue(currentEssRaces.contains(get(ess, "R11", "Default"))); // first unscheduled in series with the last race in the preceding series scheduled
}
@Test
public void testSchedulingAllIsafQualificationRacesButOne() {
for (int i=1; i<=4; i++) {
get(isaf, "Q"+i, "Yellow").setStatus(RaceLogRaceStatus.FINISHED);
get(isaf, "Q"+i, "Blue").setStatus(RaceLogRaceStatus.FINISHED);
}
get(isaf, "Q5", "Blue").setStatus(RaceLogRaceStatus.SCHEDULED);
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> currentIsafRaces = currentRaces.stream().filter(r->r.getRaceGroup() == isaf).collect(Collectors.toSet());
assertEquals(4, currentIsafRaces.size());
assertTrue(currentIsafRaces.contains(get(isaf, "Q5", "Yellow"))); // it's unscheduled; its predecessor Q4/Yellow is FINISHED; show it
assertTrue(currentIsafRaces.contains(get(isaf, "Q5", "Blue"))); // it's scheduled
assertTrue(currentIsafRaces.contains(get(isaf, "F6", "Gold"))); // Q5/Blue is a scheduled immediate predecessor
assertTrue(currentIsafRaces.contains(get(isaf, "F6", "Silver"))); // Q5/Blue is a scheduled immediate predecessor
}
@Test
public void testFinishingAllIsafQualificationAndFinalRaces() {
// the medal race shall be the only one remaining
for (int i=1; i<=5; i++) {
get(isaf, "Q"+i, "Yellow").setStatus(RaceLogRaceStatus.FINISHED);
get(isaf, "Q"+i, "Blue").setStatus(RaceLogRaceStatus.FINISHED);
get(isaf, "F"+(i+5), "Gold").setStatus(RaceLogRaceStatus.FINISHED);
get(isaf, "F"+(i+5), "Silver").setStatus(RaceLogRaceStatus.FINISHED);
}
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> currentIsafRaces = currentRaces.stream().filter(r->r.getRaceGroup() == isaf).collect(Collectors.toSet());
assertEquals(1, currentIsafRaces.size());
assertTrue(currentIsafRaces.contains(get(isaf, "M1", "Medal")));
}
@Test
public void testFinishingFirstLeagueRace() {
// with all races unscheduled we can expect the first race of each regatta's first series to show
get(league, "F1", "Red").setStatus(RaceLogRaceStatus.FINISHED);
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> currentLeagueRaces = currentRaces.stream().filter(r->r.getRaceGroup() == league).collect(Collectors.toSet());
assertEquals(1, currentLeagueRaces.size());
assertTrue(currentLeagueRaces.contains(get(league, "F1", "Green"))); // Red is finished, don't show it anymore
}
@Test
public void testSkippingSecondLeagueRace() {
// with all races unscheduled we can expect the first race of each regatta's first series to show
get(league, "F1", "Red").setStatus(RaceLogRaceStatus.FINISHED);
get(league, "F1", "Blue").setStatus(RaceLogRaceStatus.SCHEDULED);
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> currentLeagueRaces = currentRaces.stream().filter(r->r.getRaceGroup() == league).collect(Collectors.toSet());
assertEquals(3, currentLeagueRaces.size());
assertTrue(currentLeagueRaces.contains(get(league, "F1", "Green"))); // Red is finished, don't show it anymore; show the next one instead which is Green
assertTrue(currentLeagueRaces.contains(get(league, "F1", "Blue"))); // show a scheduled race
assertTrue(currentLeagueRaces.contains(get(league, "F2", "Red"))); // This is the next one after the scheduled F1/Blue
}
@Test
public void testStartInSecondSeriesOf49erRaces() {
// with all races unscheduled we start with the first race of the second series
// we can expect the first race of each regatta's series to show except the first series
get(isaf, "F6", "Gold").setStatus(RaceLogRaceStatus.SCHEDULED);
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> current49erRaces = currentRaces.stream().filter(r->r.getRaceGroup() == isaf).collect(Collectors.toSet());
assertEquals(3, current49erRaces.size());
assertTrue(current49erRaces.contains(get(isaf, "F6", "Gold"))); // it's scheduled, so it shows
assertTrue(current49erRaces.contains(get(isaf, "F6", "Silver"))); // it's unscheduled, first in fleet
assertTrue(current49erRaces.contains(get(isaf, "F7", "Gold"))); // predecessor F7/Gold is scheduled, so show this as next in fleet
}
@Test
public void testSchedulingSilverFleetKnockoutFinalRaceAlsoShowsGoldRace() {
// with nothing scheduled in earlier fleets, F1 Gold shall be scheduled when (erroneously or on purpose)
// the later race F1 Silver is already scheduled.
get(ess, "F1", "Silver").setStatus(RaceLogRaceStatus.SCHEDULED);
final Set<SimpleFilterableRace> currentRaces = fixture.getCurrentRaces();
final Set<SimpleFilterableRace> current49erRaces = currentRaces.stream().filter(r->r.getRaceGroup() == ess).collect(Collectors.toSet());
assertEquals(3, current49erRaces.size());
assertTrue(current49erRaces.contains(get(ess, "F1", "Gold"))); // it's unscheduled but the first in an already started series
assertTrue(current49erRaces.contains(get(ess, "F1", "Silver"))); // it's scheduled
assertTrue(current49erRaces.contains(get(ess, "R11", "Default"))); // preceding series' last column has a scheduled race
}
private SimpleFilterableRace get(RaceGroup raceGroup, String raceColumnName, String fleetName) {
for (final SeriesWithRows series : raceGroup.getSeries()) {
final Fleet fleet = getFleet(series, fleetName);
if (fleet != null) {
final RaceRow raceRow = series.getRaceRow(fleet);
if (raceRow != null) {
for (final RaceCell raceCell : raceRow.getCells()) {
if (raceCell.getName().equals(raceColumnName)) {
return races.get(new RaceGroupSeriesFleetRaceColumn(raceGroup, series, fleet, raceColumnName));
}
}
}
}
}
return null;
}
private Fleet getFleet(SeriesWithRows series, String fleetName) {
for (final Fleet fleet : series.getFleets()) {
if (fleet.getName().equals(fleetName)) {
return fleet;
}
}
return null;
}
private RaceRow createRaceRow(final int firstRaceColumnNumber, final int numberOfRaces,
final String raceColumnNamePrefix, final String fleetName) {
final List<RaceCell> _505RaceCells = new ArrayList<>();
for (int i=0; i < numberOfRaces; i++) {
_505RaceCells.add(new RaceCellImpl(raceColumnNamePrefix+(i+firstRaceColumnNumber),
/* race log */ null, /* factor */ firstRaceColumnNumber, /* explicitFactor */ null,
/* zeroBasedIndexInFleet */ i));
}
final RaceRowImpl _505RaceRow = new RaceRowImpl(new FleetImpl(fleetName), _505RaceCells);
return _505RaceRow;
}
}
@@ -0,0 +1,75 @@
package com.sap.sailing.domain.base.racegroup.impl;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.SeriesBase;
import com.sap.sailing.domain.base.racegroup.FilterableRace;
import com.sap.sailing.domain.base.racegroup.RaceGroup;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
public class SimpleFilterableRace implements FilterableRace {
private final RaceGroup raceGroup;
private final SeriesBase series;
private final Fleet fleet;
private RaceLogRaceStatus status;
private final int zeroBasedIndexInFleet;
private final int zeroBasedSeriesIndex;
private final String raceColumnName;
public SimpleFilterableRace(RaceGroup raceGroup, SeriesBase series, Fleet fleet, int zeroBasedIndexInFleet,
int zeroBasedSeriesIndex, String raceColumnName) {
super();
this.raceGroup = raceGroup;
this.series = series;
this.fleet = fleet;
this.zeroBasedIndexInFleet = zeroBasedIndexInFleet;
this.zeroBasedSeriesIndex = zeroBasedSeriesIndex;
this.raceColumnName = raceColumnName;
this.status = RaceLogRaceStatus.UNSCHEDULED;
}
@Override
public RaceGroup getRaceGroup() {
return raceGroup;
}
@Override
public SeriesBase getSeries() {
return series;
}
@Override
public Fleet getFleet() {
return fleet;
}
@Override
public RaceLogRaceStatus getStatus() {
return status;
}
public void setStatus(RaceLogRaceStatus status) {
this.status = status;
}
@Override
public int getZeroBasedIndexInFleet() {
return zeroBasedIndexInFleet;
}
@Override
public int getZeroBasedSeriesIndex() {
return zeroBasedSeriesIndex;
}
@Override
public String getRaceColumnName() {
return raceColumnName;
}
@Override
public String toString() {
return "SimpleFilterableRace ["+getRaceGroup().getName() + " - " + getSeries().getName() + " - " +
getFleet().getName() + " - " + getRaceColumnName() + "]";
}
}
@@ -83,7 +83,7 @@ public class DependentStartTimeFinderTest {
expectedDependingOnRaces.add(new SimpleRaceLogIdentifierImpl("A", "", ""));
assertNull(result.getStartTime());
assertEquals(bAfterA, result.getStartTimeDiff());
assertEquals(expectedDependingOnRaces, result.getRacesDependingOn());
assertEquals(expectedDependingOnRaces, result.getDependingOnRaces());
// assertEquals(5000, result.getStartTimeDiff().asMillis());
finder = new StartTimeFinder(raceLogResolver, raceLogC);
@@ -96,7 +96,7 @@ public class DependentStartTimeFinderTest {
result = finder.analyze();
assertNull(result.getStartTime());
assertEquals(cAfterB, result.getStartTimeDiff());
assertEquals(expectedDependingOnRaces, result.getRacesDependingOn());
assertEquals(expectedDependingOnRaces, result.getDependingOnRaces());
TimePoint now = MillisecondsTimePoint.now();
raceLogA.add(new RaceLogStartTimeEventImpl(now, now, author, "12", 12, new MillisecondsTimePoint(5000),
@@ -175,7 +175,7 @@ public class DependentStartTimeFinderTest {
StartTimeFinderResult result = finder.analyze();
assertEquals(19000, result.getStartTime().asMillis());
assertEquals(8000, result.getStartTimeDiff().asMillis());
assertEquals(expectedDependingOnRaces, result.getRacesDependingOn());
assertEquals(expectedDependingOnRaces, result.getDependingOnRaces());
// now C -> B
finder = new StartTimeFinder(raceLogResolver, raceLogC);
@@ -187,7 +187,7 @@ public class DependentStartTimeFinderTest {
result = finder.analyze();
assertEquals(11000, result.getStartTime().asMillis());
assertEquals(6000, result.getStartTimeDiff().asMillis());
assertEquals(expectedDependingOnRaces, result.getRacesDependingOn());
assertEquals(expectedDependingOnRaces, result.getDependingOnRaces());
// now B
finder = new StartTimeFinder(raceLogResolver, raceLogB);
@@ -198,6 +198,6 @@ public class DependentStartTimeFinderTest {
result = finder.analyze();
assertEquals(5000, result.getStartTime().asMillis());
assertNull(result.getStartTimeDiff());
assertEquals(expectedDependingOnRaces, result.getRacesDependingOn());
assertEquals(expectedDependingOnRaces, result.getDependingOnRaces());
}
}
@@ -37,6 +37,7 @@ import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
import com.sap.sailing.domain.base.configuration.impl.EmptyRegattaConfiguration;
import com.sap.sailing.domain.base.configuration.impl.RegattaConfigurationImpl;
import com.sap.sailing.domain.base.configuration.procedures.ESSConfiguration;
import com.sap.sailing.domain.common.CourseDesignerMode;
import com.sap.sailing.domain.common.racelog.Flags;
import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
@@ -177,7 +178,7 @@ public class RaceStateTest {
state.forceNewStartTime(nowMock, new MillisecondsTimePoint(1));
Thread.sleep(100);
state.setFinishedTime(new MillisecondsTimePoint(10));
state.setCourseDesign(nowMock, mock(CourseBase.class));
state.setCourseDesign(nowMock, mock(CourseBase.class), CourseDesignerMode.ADMIN_CONSOLE);
state.setAdvancePass(mock(TimePoint.class));
@@ -19,8 +19,8 @@ import com.sap.sailing.domain.base.impl.CompetitorImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class CompetitorDeregistrationTest extends AbstractRegattaLogTrackingTest {
private final Competitor competitor = new CompetitorImpl("comp", "Comp", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
private final Competitor competitor2 = new CompetitorImpl("comp2", "Comp2", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
private final Competitor competitor = new CompetitorImpl("comp", "Comp", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
private final Competitor competitor2 = new CompetitorImpl("comp2", "Comp2", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
@Test
public void testCompetitorDeregistration() {

Some files were not shown because too many files have changed in this diff Show More