diff options
author | Iván Ávalos <avalos@disroot.org> | 2024-06-14 21:08:21 -0600 |
---|---|---|
committer | Iván Ávalos <avalos@disroot.org> | 2024-06-14 21:08:21 -0600 |
commit | 471dc4ca7b6cfd656cc2c04c526fe56ee538991c (patch) | |
tree | 4766fa7209e2eaab65269db456cf0436e6a64a49 | |
parent | 447c7e15fcec8fc72d0457bb7dbf166cbea84acd (diff) | |
parent | 64528b96da4a742070d5845a876b07ca66ad0be3 (diff) | |
download | trackermap-server-471dc4ca7b6cfd656cc2c04c526fe56ee538991c.tar.gz trackermap-server-471dc4ca7b6cfd656cc2c04c526fe56ee538991c.tar.bz2 trackermap-server-471dc4ca7b6cfd656cc2c04c526fe56ee538991c.zip |
Merge tag 'v6.2'
80 files changed, 1410 insertions, 809 deletions
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e50354cbd..8bc638a65 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -18,4 +18,4 @@ jobs: distribution: temurin java-version: 11 cache: gradle - - run: ./gradlew build --no-daemon --warning-mode=fail + - run: ./gradlew build --no-daemon diff --git a/build.gradle b/build.gradle index 2fe2bfaab..029c4ae6d 100644 --- a/build.gradle +++ b/build.gradle @@ -9,13 +9,17 @@ repositories { mavenCentral() } -sourceCompatibility = "11" +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + compileJava.options.encoding = "UTF-8" jar.destinationDirectory = file("$projectDir/target") checkstyle { - toolVersion = "10.15.0" - configFile = "gradle/checkstyle.xml" as File + toolVersion = "10.17.0" + configFile = file("gradle/checkstyle.xml") checkstyleTest.enabled = false } @@ -27,10 +31,10 @@ enforce { ext { guiceVersion = "7.0.0" - jettyVersion = "11.0.20" - jerseyVersion = "3.1.5" - jacksonVersion = "2.15.3" // same version as jersey-media-json-jackson dependency - protobufVersion = "4.26.1" + jettyVersion = "11.0.21" + jerseyVersion = "3.1.7" + jacksonVersion = "2.17.1" // same version as jersey-media-json-jackson dependency + protobufVersion = "4.27.0" jxlsVersion = "2.14.0" // version 3 requires java 17 junitVersion = "5.10.2" } @@ -42,18 +46,17 @@ protobuf { } dependencies { - implementation "commons-codec:commons-codec:1.16.1" + implementation "commons-codec:commons-codec:1.17.0" implementation "com.h2database:h2:2.2.224" - implementation "com.mysql:mysql-connector-j:8.3.0" - implementation "org.mariadb.jdbc:mariadb-java-client:3.3.3" + implementation "com.mysql:mysql-connector-j:8.4.0" + implementation "org.mariadb.jdbc:mariadb-java-client:3.4.0" implementation "org.postgresql:postgresql:42.7.3" - implementation "com.microsoft.sqlserver:mssql-jdbc:12.6.1.jre11" + implementation "com.microsoft.sqlserver:mssql-jdbc:12.6.2.jre11" implementation "com.zaxxer:HikariCP:5.1.0" - implementation "io.netty:netty-all:4.1.108.Final" - implementation "org.slf4j:slf4j-jdk14:2.0.12" + implementation "io.netty:netty-all:4.1.110.Final" + implementation "org.slf4j:slf4j-jdk14:2.0.13" implementation "com.google.inject:guice:$guiceVersion" implementation "com.google.inject.extensions:guice-servlet:$guiceVersion" - implementation "org.owasp.encoder:encoder:1.2.3" implementation "org.glassfish:jakarta.json:2.0.1" implementation "com.sun.mail:jakarta.mail:2.0.1" implementation "org.eclipse.jetty:jetty-server:$jettyVersion" @@ -66,7 +69,7 @@ dependencies { implementation "org.glassfish.jersey.containers:jersey-container-servlet:$jerseyVersion" implementation "org.glassfish.jersey.media:jersey-media-json-jackson:$jerseyVersion" implementation "org.glassfish.jersey.inject:jersey-hk2:$jerseyVersion" - implementation "org.glassfish.hk2:guice-bridge:3.0.5" // same version as jersey-hk2 + implementation "org.glassfish.hk2:guice-bridge:3.1.0" // same version as jersey-hk2 implementation "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jacksonVersion" implementation "com.fasterxml.jackson.datatype:jackson-datatype-jakarta-jsonp:$jacksonVersion" implementation "org.liquibase:liquibase-core:4.23.2" // upgrade has issues @@ -76,23 +79,24 @@ dependencies { implementation "org.apache.velocity:velocity-engine-core:2.3" implementation "org.apache.velocity.tools:velocity-tools-generic:3.1" implementation "org.apache.commons:commons-collections4:4.4" - implementation "org.mnode.ical4j:ical4j:3.2.17" + implementation "org.mnode.ical4j:ical4j:3.2.18" implementation "org.locationtech.spatial4j:spatial4j:0.8" implementation "org.locationtech.jts:jts-core:1.19.0" implementation "net.java.dev.jna:jna-platform:5.14.0" implementation "com.github.jnr:jnr-posix:3.1.19" implementation "com.google.protobuf:protobuf-java:$protobufVersion" - implementation "com.amazonaws:aws-java-sdk-sns:1.12.694" + implementation "com.amazonaws:aws-java-sdk-sns:1.12.733" implementation "org.apache.kafka:kafka-clients:3.7.0" implementation "com.hivemq:hivemq-mqtt-client:1.3.3" - implementation "redis.clients:jedis:5.1.2" - implementation "com.google.firebase:firebase-admin:9.2.0" - implementation "com.nimbusds:oauth2-oidc-sdk:11.10.1" - implementation "com.rabbitmq:amqp-client:5.20.0" + implementation "redis.clients:jedis:5.1.3" + implementation "com.google.firebase:firebase-admin:9.3.0" + implementation "com.nimbusds:oauth2-oidc-sdk:11.12" + implementation "com.rabbitmq:amqp-client:5.21.0" implementation "com.warrenstrange:googleauth:1.5.0" + implementation "com.google.openlocationcode:openlocationcode:1.0.4" testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-engine:$junitVersion" - testImplementation "org.mockito:mockito-core:5.11.0" + testImplementation "org.mockito:mockito-core:5.12.0" } test { @@ -109,7 +113,7 @@ jar { manifest { attributes( "Main-Class": "org.traccar.Main", - "Implementation-Version": "6.0", + "Implementation-Version": "6.2", "Class-Path": configurations.runtimeClasspath.files.collect { "lib/$it.name" }.join(" ")) } } @@ -1,16 +1,12 @@ <?xml version='1.0' encoding='UTF-8'?> - <!DOCTYPE properties SYSTEM 'http://java.sun.com/dtd/properties.dtd'> - <properties> - <entry key='config.default'>./setup/default.xml</entry> - <entry key='web.path'>./traccar-web/simple</entry> <entry key='web.debug'>true</entry> <entry key='web.console'>true</entry> - <entry key='geocoder.type'>test</entry> + <entry key='geocoder.type'>pluscodes</entry> <entry key='media.path'>./target/media</entry> diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar Binary files differindex 41d9927a4..e6441136f 100644 --- a/gradle/wrapper/gradle-wrapper.jar +++ b/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661ee..0d1842103 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd32c..25da30dbd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/schema/changelog-6.2.xml b/schema/changelog-6.2.xml new file mode 100644 index 000000000..568d9a3a0 --- /dev/null +++ b/schema/changelog-6.2.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<databaseChangeLog + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog + http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd" + logicalFilePath="changelog-6.2"> + + <changeSet author="author" id="changelog-6.2"> + + <dropColumn tableName="tc_servers" columnName="twelvehourformat" /> + <dropColumn tableName="tc_users" columnName="twelvehourformat" /> + + <addColumn tableName="tc_attributes"> + <column name="priority" type="INT" defaultValueNumeric="0"> + <constraints nullable="false" /> + </column> + </addColumn> + + </changeSet> + +</databaseChangeLog> diff --git a/schema/changelog-master.xml b/schema/changelog-master.xml index 559d90923..e9ae52569 100644 --- a/schema/changelog-master.xml +++ b/schema/changelog-master.xml @@ -43,4 +43,6 @@ <include file="changelog-5.10.xml" relativeToChangelogFile="true" /> <include file="changelog-5.11.xml" relativeToChangelogFile="true" /> + <include file="changelog-6.2.xml" relativeToChangelogFile="true" /> + </databaseChangeLog> diff --git a/setup/cloud-init.yaml b/setup/cloud-init.yaml index 1246fb1b7..3d55f5f6f 100644 --- a/setup/cloud-init.yaml +++ b/setup/cloud-init.yaml @@ -5,14 +5,10 @@ write_files: <?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE properties SYSTEM 'http://java.sun.com/dtd/properties.dtd'> <properties> - - <entry key="config.default">./conf/default.xml</entry> - <entry key='database.driver'>com.mysql.jdbc.Driver</entry> <entry key='database.url'>jdbc:mysql://localhost/traccar?zeroDateTimeBehavior=round&serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false&allowMultiQueries=true&autoReconnect=true&useUnicode=yes&characterEncoding=UTF-8&sessionVariables=sql_mode=''</entry> <entry key='database.user'>root</entry> <entry key='database.password'>root</entry> - </properties> path: /root/traccar.xml diff --git a/setup/default.xml b/setup/default.xml deleted file mode 100644 index 092b4f494..000000000 --- a/setup/default.xml +++ /dev/null @@ -1,302 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> - -<!DOCTYPE properties SYSTEM 'http://java.sun.com/dtd/properties.dtd'> - -<properties> - - <!-- - - DO NOT MODIFY THIS FILE. Use traccar.xml instead. - - --> - - <entry key='web.port'>8082</entry> - <entry key='web.path'>./web</entry> - <entry key='web.sanitize'>false</entry> - <entry key='web.persistSession'>false</entry> - <entry key='web.showUnknownDevices'>true</entry> - - <entry key='geocoder.enable'>true</entry> - <entry key='geocoder.type'>locationiq</entry> - <entry key='geocoder.key'>pk.689d849289c8c63708068b2ff1f63b2d</entry> - <entry key='geocoder.onRequest'>true</entry> - <entry key='geocoder.ignorePositions'>true</entry> - - <entry key='logger.level'>info</entry> - <entry key='logger.file'>./logs/tracker-server.log</entry> - <entry key='logger.rotate'>true</entry> - - <entry key='filter.enable'>true</entry> - <entry key='filter.future'>86400</entry> - - <entry key='event.ignoreDuplicateAlerts'>true</entry> - - <entry key='media.path'>./media</entry> - - <entry key='notificator.types'>web,mail,command</entry> - - <entry key='server.statistics'>https://www.traccar.org/analytics/</entry> - - <entry key='commands.queueing'>true</entry> - - <entry key='database.ignoreUnknown'>true</entry> - <entry key='database.changelog'>./schema/changelog-master.xml</entry> - - <entry key='gps103.port'>5001</entry> - <entry key='tk103.port'>5002</entry> - <entry key='gl100.port'>5003</entry> - <entry key='gl200.port'>5004</entry> - <entry key='t55.port'>5005</entry> - <entry key='xexun.port'>5006</entry> - <entry key='xexun.extended'>false</entry> - <entry key='totem.port'>5007</entry> - <entry key='enfora.port'>5008</entry> - <entry key='meiligao.port'>5009</entry> - <entry key='trv.port'>5010</entry> - <entry key='suntech.port'>5011</entry> - <entry key='progress.port'>5012</entry> - <entry key='h02.port'>5013</entry> - <entry key='jt600.port'>5014</entry> - <entry key='huabao.port'>5015</entry> - <entry key='v680.port'>5016</entry> - <entry key='pt502.port'>5017</entry> - <entry key='tr20.port'>5018</entry> - <entry key='navis.port'>5019</entry> - <entry key='meitrack.port'>5020</entry> - <entry key='skypatrol.port'>5021</entry> - <entry key='gt02.port'>5022</entry> - <entry key='gt06.port'>5023</entry> - <entry key='megastek.port'>5024</entry> - <entry key='navigil.port'>5025</entry> - <entry key='gpsgate.port'>5026</entry> - <entry key='teltonika.port'>5027</entry> - <entry key='mta6.port'>5028</entry> - <entry key='tzone.port'>5029</entry> - <entry key='tlt2h.port'>5030</entry> - <entry key='taip.port'>5031</entry> - <entry key='wondex.port'>5032</entry> - <entry key='cellocator.port'>5033</entry> - <entry key='galileo.port'>5034</entry> - <entry key='ywt.port'>5035</entry> - <entry key='tk102.port'>5036</entry> - <entry key='intellitrac.port'>5037</entry> - <entry key='gpsmta.port'>5038</entry> - <entry key='wialon.port'>5039</entry> - <entry key='carscop.port'>5040</entry> - <entry key='apel.port'>5041</entry> - <entry key='manpower.port'>5042</entry> - <entry key='globalsat.port'>5043</entry> - <entry key='atrack.port'>5044</entry> - <entry key='pt3000.port'>5045</entry> - <entry key='ruptela.port'>5046</entry> - <entry key='topflytech.port'>5047</entry> - <entry key='laipac.port'>5048</entry> - <entry key='aplicom.port'>5049</entry> - <entry key='gotop.port'>5050</entry> - <entry key='sanav.port'>5051</entry> - <entry key='gator.port'>5052</entry> - <entry key='noran.port'>5053</entry> - <entry key='m2m.port'>5054</entry> - <entry key='osmand.port'>5055</entry> - <entry key='easytrack.port'>5056</entry> - <entry key='gpsmarker.port'>5057</entry> - <entry key='khd.port'>5058</entry> - <entry key='piligrim.port'>5059</entry> - <entry key='stl060.port'>5060</entry> - <entry key='cartrack.port'>5061</entry> - <entry key='minifinder.port'>5062</entry> - <entry key='haicom.port'>5063</entry> - <entry key='eelink.port'>5064</entry> - <entry key='box.port'>5065</entry> - <entry key='freedom.port'>5066</entry> - <entry key='telic.port'>5067</entry> - <entry key='trackbox.port'>5068</entry> - <entry key='visiontek.port'>5069</entry> - <entry key='orion.port'>5070</entry> - <entry key='riti.port'>5071</entry> - <entry key='ulbotech.port'>5072</entry> - <entry key='tramigo.port'>5073</entry> - <entry key='tr900.port'>5074</entry> - <entry key='ardi01.port'>5075</entry> - <entry key='xt013.port'>5076</entry> - <entry key='autofon.port'>5077</entry> - <entry key='gosafe.port'>5078</entry> - <entry key='tt8850.port'>5079</entry> - <entry key='bce.port'>5080</entry> - <entry key='xirgo.port'>5081</entry> - <entry key='calamp.port'>5082</entry> - <entry key='mtx.port'>5083</entry> - <entry key='tytan.port'>5084</entry> - <entry key='avl301.port'>5085</entry> - <entry key='castel.port'>5086</entry> - <entry key='mxt.port'>5087</entry> - <entry key='cityeasy.port'>5088</entry> - <entry key='aquila.port'>5089</entry> - <entry key='flextrack.port'>5090</entry> - <entry key='blackkite.port'>5091</entry> - <entry key='adm.port'>5092</entry> - <entry key='watch.port'>5093</entry> - <entry key='t800x.port'>5094</entry> - <entry key='upro.port'>5095</entry> - <entry key='auro.port'>5096</entry> - <entry key='disha.port'>5097</entry> - <entry key='thinkrace.port'>5098</entry> - <entry key='pathaway.port'>5099</entry> - <entry key='arnavi.port'>5100</entry> - <entry key='nvs.port'>5101</entry> - <entry key='kenji.port'>5102</entry> - <entry key='astra.port'>5103</entry> - <entry key='homtecs.port'>5104</entry> - <entry key='fox.port'>5105</entry> - <entry key='gnx.port'>5106</entry> - <entry key='arknav.port'>5107</entry> - <entry key='supermate.port'>5108</entry> - <entry key='appello.port'>5109</entry> - <entry key='idpl.port'>5110</entry> - <entry key='huasheng.port'>5111</entry> - <entry key='l100.port'>5112</entry> - <entry key='granit.port'>5113</entry> - <entry key='carcell.port'>5114</entry> - <entry key='obddongle.port'>5115</entry> - <entry key='hunterpro.port'>5116</entry> - <entry key='raveon.port'>5117</entry> - <entry key='cradlepoint.port'>5118</entry> - <entry key='arknavx8.port'>5119</entry> - <entry key='autograde.port'>5120</entry> - <entry key='oigo.port'>5121</entry> - <entry key='jpkorjar.port'>5122</entry> - <entry key='cguard.port'>5123</entry> - <entry key='fifotrack.port'>5124</entry> - <entry key='smokey.port'>5125</entry> - <entry key='extremtrac.port'>5126</entry> - <entry key='trakmate.port'>5127</entry> - <entry key='at2000.port'>5128</entry> - <entry key='maestro.port'>5129</entry> - <entry key='ais.port'>5130</entry> - <entry key='gt30.port'>5131</entry> - <entry key='tmg.port'>5132</entry> - <entry key='pretrace.port'>5133</entry> - <entry key='pricol.port'>5134</entry> - <entry key='siwi.port'>5135</entry> - <entry key='starlink.port'>5136</entry> - <entry key='dmt.port'>5137</entry> - <entry key='xt2400.port'>5138</entry> - <entry key='dmthttp.port'>5139</entry> - <entry key='alematics.port'>5140</entry> - <entry key='gps056.port'>5141</entry> - <entry key='flexcomm.port'>5142</entry> - <entry key='vt200.port'>5143</entry> - <entry key='owntracks.port'>5144</entry> - <entry key='vtfms.port'>5145</entry> - <entry key='tlv.port'>5146</entry> - <entry key='esky.port'>5147</entry> - <entry key='genx.port'>5148</entry> - <entry key='flespi.port'>5149</entry> - <entry key='dway.port'>5150</entry> - <entry key='recoda.port'>5151</entry> - <entry key='oko.port'>5152</entry> - <entry key='ivt401.port'>5153</entry> - <entry key='sigfox.port'>5154</entry> - <entry key='t57.port'>5155</entry> - <entry key='spot.port'>5156</entry> - <entry key='m2c.port'>5157</entry> - <entry key='austinnb.port'>5158</entry> - <entry key='opengts.port'>5159</entry> - <entry key='cautela.port'>5160</entry> - <entry key='continental.port'>5161</entry> - <entry key='egts.port'>5162</entry> - <entry key='robotrack.port'>5163</entry> - <entry key='pt60.port'>5164</entry> - <entry key='telemax.port'>5165</entry> - <entry key='sabertek.port'>5166</entry> - <entry key='retranslator.port'>5167</entry> - <entry key='svias.port'>5168</entry> - <entry key='eseal.port'>5169</entry> - <entry key='freematics.port'>5170</entry> - <entry key='avema.port'>5171</entry> - <entry key='autotrack.port'>5172</entry> - <entry key='tek.port'>5173</entry> - <entry key='wristband.port'>5174</entry> - <entry key='lacak.port'>5175</entry> - <entry key='milesmate.port'>5176</entry> - <entry key='anytrek.port'>5177</entry> - <entry key='smartsole.port'>5178</entry> - <entry key='its.port'>5179</entry> - <entry key='xrb28.port'>5180</entry> - <entry key='c2stek.port'>5181</entry> - <entry key='nyitech.port'>5182</entry> - <entry key='neos.port'>5183</entry> - <entry key='satsol.port'>5184</entry> - <entry key='globalstar.port'>5185</entry> - <entry key='sanul.port'>5186</entry> - <entry key='minifinder2.port'>5187</entry> - <entry key='radar.port'>5188</entry> - <entry key='techtlt.port'>5189</entry> - <entry key='starcom.port'>5190</entry> - <entry key='mictrack.port'>5191</entry> - <entry key='plugin.port'>5192</entry> - <entry key='leafspy.port'>5193</entry> - <entry key='naviset.port'>5194</entry> - <entry key='racedynamics.port'>5195</entry> - <entry key='rst.port'>5196</entry> - <entry key='pt215.port'>5197</entry> - <entry key='pacifictrack.port'>5198</entry> - <entry key='topin.port'>5199</entry> - <entry key='outsafe.port'>5200</entry> - <entry key='solarpowered.port'>5201</entry> - <entry key='motor.port'>5202</entry> - <entry key='omnicomm.port'>5203</entry> - <entry key='s168.port'>5204</entry> - <entry key='vnet.port'>5205</entry> - <entry key='blue.port'>5206</entry> - <entry key='pst.port'>5207</entry> - <entry key='dingtek.port'>5208</entry> - <entry key='wli.port'>5209</entry> - <entry key='niot.port'>5210</entry> - <entry key='portman.port'>5211</entry> - <entry key='moovbox.port'>5212</entry> - <entry key='futureway.port'>5213</entry> - <entry key='polte.port'>5214</entry> - <entry key='net.port'>5215</entry> - <entry key='mobilogix.port'>5216</entry> - <entry key='swiftech.port'>5217</entry> - <entry key='iotm.port'>5218</entry> - <entry key='dolphin.port'>5219</entry> - <entry key='ennfu.port'>5220</entry> - <entry key='navtelecom.port'>5221</entry> - <entry key='startek.port'>5222</entry> - <entry key='gs100.port'>5223</entry> - <entry key='mavlink2.port'>5224</entry> - <entry key='uux.port'>5225</entry> - <entry key='r12w.port'>5226</entry> - <entry key='flexiblereport.port'>5227</entry> - <entry key='thinkpower.port'>5228</entry> - <entry key='stb.port'>5229</entry> - <entry key='b2316.port'>5230</entry> - <entry key='hoopo.port'>5231</entry> - <entry key='dualcam.port'>5232</entry> - <entry key='xexun2.port'>5233</entry> - <entry key='techtocruz.port'>5234</entry> - <entry key='flexapi.port'>5235</entry> - <entry key='dsf22.port'>5236</entry> - <entry key='jido.port'>5237</entry> - <entry key='armoli.port'>5238</entry> - <entry key='teratrack.port'>5239</entry> - <entry key='envotech.port'>5240</entry> - <entry key='bstpl.port'>5241</entry> - <entry key='thuraya.port'>5242</entry> - <entry key='ndtpv6.port'>5243</entry> - <entry key='g1rus.port'>5244</entry> - <entry key='rftrack.port'>5245</entry> - <entry key='vlt.port'>5246</entry> - <entry key='transync.port'>5247</entry> - <entry key='t622iridium.port'>5248</entry> - <entry key='pui.port'>5249</entry> - <entry key='nto.port'>5250</entry> - <entry key='ramac.port'>5251</entry> - <entry key='positrex.port'>5252</entry> - <entry key='dragino.port'>5253</entry> - <entry key='fleetguide.port'>5254</entry> - <entry key='valtrack.port'>5255</entry> - -</properties> diff --git a/setup/package.sh b/setup/package.sh index 5b3c9bcde..1cac75713 100755 --- a/setup/package.sh +++ b/setup/package.sh @@ -88,7 +88,6 @@ prepare () { cp ../schema/* out/schema cp -r ../templates/* out/templates cp -r ../traccar-web/build/* out/web - cp default.xml out/conf cp traccar.xml out/conf if [ $PLATFORM = "all" -o $PLATFORM = "windows-64" ]; then diff --git a/setup/traccar.iss b/setup/traccar.iss index 466a40c30..ad8e2552b 100644 --- a/setup/traccar.iss +++ b/setup/traccar.iss @@ -1,6 +1,6 @@ [Setup] AppName=Traccar -AppVersion=6.0 +AppVersion=6.2 DefaultDirName={pf}\Traccar OutputBaseFilename=traccar-setup ArchitecturesInstallIn64BitMode=x64 diff --git a/setup/traccar.xml b/setup/traccar.xml index aab9ba311..d5bd87e9c 100644 --- a/setup/traccar.xml +++ b/setup/traccar.xml @@ -1,22 +1,8 @@ <?xml version='1.0' encoding='UTF-8'?> - <!DOCTYPE properties SYSTEM 'http://java.sun.com/dtd/properties.dtd'> - <properties> - <entry key='config.default'>./conf/default.xml</entry> - - <!-- - - This is the main configuration file. All your configuration parameters should be placed in this file. - - Default configuration parameters are located in the "default.xml" file. You should not modify it to avoid issues - with upgrading to a new version. Parameters in the main config file override values in the default file. Do not - remove "config.default" parameter from this file unless you know what you are doing. - - For list of available parameters see following page: https://www.traccar.org/configuration-file/ - - --> + <!-- Documentation: https://www.traccar.org/configuration-file/ --> <entry key='database.driver'>org.h2.Driver</entry> <entry key='database.url'>jdbc:h2:./data/database</entry> diff --git a/src/main/java/org/traccar/EventLoopGroupFactory.java b/src/main/java/org/traccar/EventLoopGroupFactory.java index 482559253..7f596cd83 100644 --- a/src/main/java/org/traccar/EventLoopGroupFactory.java +++ b/src/main/java/org/traccar/EventLoopGroupFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2012 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,18 +20,18 @@ import io.netty.channel.nio.NioEventLoopGroup; public final class EventLoopGroupFactory { - private static EventLoopGroup bossGroup = new NioEventLoopGroup(); - private static EventLoopGroup workerGroup = new NioEventLoopGroup(); + private static final EventLoopGroup BOSS_GROUP = new NioEventLoopGroup(); + private static final EventLoopGroup WORKER_GROUP = new NioEventLoopGroup(); private EventLoopGroupFactory() { } public static EventLoopGroup getBossGroup() { - return bossGroup; + return BOSS_GROUP; } public static EventLoopGroup getWorkerGroup() { - return workerGroup; + return WORKER_GROUP; } } diff --git a/src/main/java/org/traccar/Main.java b/src/main/java/org/traccar/Main.java index 33fcf654f..a22194d9e 100644 --- a/src/main/java/org/traccar/Main.java +++ b/src/main/java/org/traccar/Main.java @@ -17,6 +17,7 @@ package org.traccar; import com.google.inject.Guice; import com.google.inject.Injector; +import com.google.inject.ProvisionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.broadcast.BroadcastService; @@ -51,23 +52,22 @@ public final class Main { public static void logSystemInfo() { try { OperatingSystemMXBean operatingSystemBean = ManagementFactory.getOperatingSystemMXBean(); - LOGGER.info("Operating system" - + " name: " + operatingSystemBean.getName() - + " version: " + operatingSystemBean.getVersion() - + " architecture: " + operatingSystemBean.getArch()); + LOGGER.info( + "Operating system name: {} version: {} architecture: {}", + operatingSystemBean.getName(), operatingSystemBean.getVersion(), operatingSystemBean.getArch()); RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); - LOGGER.info("Java runtime" - + " name: " + runtimeBean.getVmName() - + " vendor: " + runtimeBean.getVmVendor() - + " version: " + runtimeBean.getVmVersion()); + LOGGER.info( + "Java runtime name: {} vendor: {} version: {}", + runtimeBean.getVmName(), runtimeBean.getVmVendor(), runtimeBean.getVmVersion()); MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); - LOGGER.info("Memory limit" - + " heap: " + memoryBean.getHeapMemoryUsage().getMax() / (1024 * 1024) + "mb" - + " non-heap: " + memoryBean.getNonHeapMemoryUsage().getMax() / (1024 * 1024) + "mb"); + LOGGER.info( + "Memory limit heap: {}mb non-heap: {}mb", + memoryBean.getHeapMemoryUsage().getMax() / (1024 * 1024), + memoryBean.getNonHeapMemoryUsage().getMax() / (1024 * 1024)); - LOGGER.info("Character encoding: " + Charset.defaultCharset().displayName()); + LOGGER.info("Character encoding: {}", Charset.defaultCharset().displayName()); } catch (Exception error) { LOGGER.warn("Failed to get system info"); @@ -115,7 +115,7 @@ public final class Main { try { injector = Guice.createInjector(new MainModule(configFile), new DatabaseModule(), new WebModule()); logSystemInfo(); - LOGGER.info("Version: " + Main.class.getPackage().getImplementationVersion()); + LOGGER.info("Version: {}", Main.class.getPackage().getImplementationVersion()); LOGGER.info("Starting server..."); var services = new ArrayList<LifecycleObject>(); @@ -142,8 +142,14 @@ public final class Main { } })); } catch (Exception e) { - LOGGER.error("Main method error", e); - throw new RuntimeException(e); + Throwable unwrapped; + if (e instanceof ProvisionException) { + unwrapped = e.getCause(); + } else { + unwrapped = e; + } + LOGGER.error("Main method error", unwrapped); + System.exit(1); } } diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java index 791d61c61..66238ab44 100644 --- a/src/main/java/org/traccar/MainModule.java +++ b/src/main/java/org/traccar/MainModule.java @@ -66,7 +66,7 @@ import org.traccar.geocoder.MapmyIndiaGeocoder; import org.traccar.geocoder.NominatimGeocoder; import org.traccar.geocoder.OpenCageGeocoder; import org.traccar.geocoder.PositionStackGeocoder; -import org.traccar.geocoder.TestGeocoder; +import org.traccar.geocoder.PlusCodesGeocoder; import org.traccar.geocoder.TomTomGeocoder; import org.traccar.geolocation.GeolocationProvider; import org.traccar.geolocation.GoogleGeolocationProvider; @@ -79,7 +79,6 @@ import org.traccar.handler.GeolocationHandler; import org.traccar.handler.SpeedLimitHandler; import org.traccar.handler.TimeHandler; import org.traccar.helper.ObjectMapperContextResolver; -import org.traccar.helper.SanitizerModule; import org.traccar.helper.WebHelper; import org.traccar.mail.LogMailManager; import org.traccar.mail.MailManager; @@ -132,11 +131,8 @@ public class MainModule extends AbstractModule { @Singleton @Provides - public static ObjectMapper provideObjectMapper(Config config) { + public static ObjectMapper provideObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); - if (config.getBoolean(Keys.WEB_SANITIZE)) { - objectMapper.registerModule(new SanitizerModule()); - } objectMapper.registerModule(new JSONPModule()); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return objectMapper; @@ -191,7 +187,7 @@ public class MainModule extends AbstractModule { @Provides public static WebServer provideWebServer(Injector injector, Config config) { - if (config.hasKey(Keys.WEB_PORT)) { + if (config.getInteger(Keys.WEB_PORT) > 0) { return new WebServer(injector, config); } return null; @@ -201,7 +197,7 @@ public class MainModule extends AbstractModule { @Provides public static Geocoder provideGeocoder(Config config, Client client, StatisticsManager statisticsManager) { if (config.getBoolean(Keys.GEOCODER_ENABLE)) { - String type = config.getString(Keys.GEOCODER_TYPE, "google"); + String type = config.getString(Keys.GEOCODER_TYPE); String url = config.getString(Keys.GEOCODER_URL); String key = config.getString(Keys.GEOCODER_KEY); String language = config.getString(Keys.GEOCODER_LANGUAGE); @@ -211,8 +207,8 @@ public class MainModule extends AbstractModule { int cacheSize = config.getInteger(Keys.GEOCODER_CACHE_SIZE); Geocoder geocoder; switch (type) { - case "test": - geocoder = new TestGeocoder(); + case "pluscodes": + geocoder = new PlusCodesGeocoder(); break; case "nominatim": geocoder = new NominatimGeocoder(client, url, key, language, cacheSize, addressFormat); diff --git a/src/main/java/org/traccar/ProcessingHandler.java b/src/main/java/org/traccar/ProcessingHandler.java index fd048d127..bb040bfff 100644 --- a/src/main/java/org/traccar/ProcessingHandler.java +++ b/src/main/java/org/traccar/ProcessingHandler.java @@ -101,8 +101,8 @@ public class ProcessingHandler extends ChannelInboundHandlerAdapter implements B GeocoderHandler.class, SpeedLimitHandler.class, MotionHandler.class, - EngineHoursHandler.class, ComputedAttributesHandler.class, + EngineHoursHandler.class, CopyAttributesHandler.class, PositionForwardingHandler.class, DatabaseHandler.class) diff --git a/src/main/java/org/traccar/ServerManager.java b/src/main/java/org/traccar/ServerManager.java index 22af66b41..1b0c441cf 100644 --- a/src/main/java/org/traccar/ServerManager.java +++ b/src/main/java/org/traccar/ServerManager.java @@ -54,7 +54,7 @@ public class ServerManager implements LifecycleObject { for (Class<?> protocolClass : ClassScanner.findSubclasses(BaseProtocol.class, "org.traccar.protocol")) { String protocolName = BaseProtocol.nameFromClass(protocolClass); if (enabledProtocols == null || enabledProtocols.contains(protocolName)) { - if (config.hasKey(Keys.PROTOCOL_PORT.withPrefix(protocolName))) { + if (config.getInteger(Keys.PROTOCOL_PORT.withPrefix(protocolName)) > 0) { BaseProtocol protocol = (BaseProtocol) injector.getInstance(protocolClass); connectorList.addAll(protocol.getConnectorList()); protocolList.put(protocol.getName(), protocol); diff --git a/src/main/java/org/traccar/api/AsyncSocketServlet.java b/src/main/java/org/traccar/api/AsyncSocketServlet.java index cd2c1639e..e1e7ee977 100644 --- a/src/main/java/org/traccar/api/AsyncSocketServlet.java +++ b/src/main/java/org/traccar/api/AsyncSocketServlet.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jetty.websocket.server.JettyWebSocketServlet; import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory; import org.traccar.api.resource.SessionResource; +import org.traccar.api.security.LoginService; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.session.ConnectionManager; @@ -27,7 +28,12 @@ import org.traccar.storage.Storage; import jakarta.inject.Inject; import jakarta.inject.Singleton; import jakarta.servlet.http.HttpSession; +import org.traccar.storage.StorageException; + +import java.io.IOException; +import java.security.GeneralSecurityException; import java.time.Duration; +import java.util.List; @Singleton public class AsyncSocketServlet extends JettyWebSocketServlet { @@ -36,25 +42,37 @@ public class AsyncSocketServlet extends JettyWebSocketServlet { private final ObjectMapper objectMapper; private final ConnectionManager connectionManager; private final Storage storage; + private final LoginService loginService; @Inject public AsyncSocketServlet( - Config config, ObjectMapper objectMapper, ConnectionManager connectionManager, Storage storage) { + Config config, ObjectMapper objectMapper, ConnectionManager connectionManager, Storage storage, + LoginService loginService) { this.config = config; this.objectMapper = objectMapper; this.connectionManager = connectionManager; this.storage = storage; + this.loginService = loginService; } @Override public void configure(JettyWebSocketServletFactory factory) { factory.setIdleTimeout(Duration.ofMillis(config.getLong(Keys.WEB_TIMEOUT))); factory.setCreator((req, resp) -> { - if (req.getSession() != null) { - Long userId = (Long) ((HttpSession) req.getSession()).getAttribute(SessionResource.USER_ID_KEY); - if (userId != null) { - return new AsyncSocket(objectMapper, connectionManager, storage, userId); + Long userId = null; + List<String> tokens = req.getParameterMap().get("token"); + if (tokens != null && !tokens.isEmpty()) { + String token = tokens.iterator().next(); + try { + userId = loginService.login(token).getUser().getId(); + } catch (StorageException | GeneralSecurityException | IOException e) { + throw new RuntimeException(e); } + } else if (req.getSession() != null) { + userId = (Long) ((HttpSession) req.getSession()).getAttribute(SessionResource.USER_ID_KEY); + } + if (userId != null) { + return new AsyncSocket(objectMapper, connectionManager, storage, userId); } return null; }); diff --git a/src/main/java/org/traccar/api/resource/CommandResource.java b/src/main/java/org/traccar/api/resource/CommandResource.java index c23d91e77..66ec0f8a3 100644 --- a/src/main/java/org/traccar/api/resource/CommandResource.java +++ b/src/main/java/org/traccar/api/resource/CommandResource.java @@ -23,6 +23,7 @@ import org.traccar.BaseProtocol; import org.traccar.ServerManager; import org.traccar.api.ExtendedObjectResource; import org.traccar.database.CommandsManager; +import org.traccar.helper.LogAction; import org.traccar.helper.model.DeviceUtil; import org.traccar.model.Command; import org.traccar.model.Device; @@ -140,6 +141,8 @@ public class CommandResource extends ExtendedObjectResource<Command> { return Response.accepted(queuedCommand).build(); } } + + LogAction.command(getUserId(), groupId, entity.getDeviceId(), entity.getType()); return Response.ok(entity).build(); } diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java index 56253152f..971c29c60 100644 --- a/src/main/java/org/traccar/api/resource/DeviceResource.java +++ b/src/main/java/org/traccar/api/resource/DeviceResource.java @@ -137,10 +137,8 @@ public class DeviceResource extends BaseObjectResource<Device> { @Path("{id}/accumulators") @PUT public Response updateAccumulators(DeviceAccumulators entity) throws Exception { - if (permissionsService.notAdmin(getUserId())) { - permissionsService.checkManager(getUserId()); - permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId()); - } + permissionsService.checkEdit(getUserId(), Device.class, false); + permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId()); Position position = storage.getObject(Position.class, new Request( new Columns.All(), new Condition.LatestPositions(entity.getDeviceId()))); @@ -171,7 +169,7 @@ public class DeviceResource extends BaseObjectResource<Device> { throw new IllegalArgumentException(); } - LogAction.resetDeviceAccumulators(getUserId(), entity.getDeviceId()); + LogAction.resetAccumulators(getUserId(), entity.getDeviceId()); return Response.noContent().build(); } diff --git a/src/main/java/org/traccar/api/resource/NotificationResource.java b/src/main/java/org/traccar/api/resource/NotificationResource.java index 43dc1fa98..a41d00cf3 100644 --- a/src/main/java/org/traccar/api/resource/NotificationResource.java +++ b/src/main/java/org/traccar/api/resource/NotificationResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; @Path("notifications") @Produces(MediaType.APPLICATION_JSON) @@ -80,8 +82,11 @@ public class NotificationResource extends ExtendedObjectResource<Notification> { @GET @Path("notificators") - public Collection<Typed> getNotificators() { - return notificatorManager.getAllNotificatorTypes(); + public Collection<Typed> getNotificators(@QueryParam("announcement") boolean announcement) { + Set<String> announcementsUnsupported = Set.of("command", "web"); + return notificatorManager.getAllNotificatorTypes().stream() + .filter(typed -> !announcement || !announcementsUnsupported.contains(typed.getType())) + .collect(Collectors.toUnmodifiableSet()); } @POST @@ -120,7 +125,9 @@ public class NotificationResource extends ExtendedObjectResource<Notification> { } } for (User user : users) { - notificatorManager.getNotificator(notificator).send(user, message, null, null); + if (!user.getTemporary()) { + notificatorManager.getNotificator(notificator).send(user, message, null, null); + } } return Response.noContent().build(); } diff --git a/src/main/java/org/traccar/api/resource/ReportResource.java b/src/main/java/org/traccar/api/resource/ReportResource.java index 55a96fa90..81f409c0f 100644 --- a/src/main/java/org/traccar/api/resource/ReportResource.java +++ b/src/main/java/org/traccar/api/resource/ReportResource.java @@ -113,7 +113,7 @@ public class ReportResource extends SimpleObjectResource<Report> { @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); - LogAction.logReport(getUserId(), "combined", from, to, deviceIds, groupIds); + LogAction.report(getUserId(), false, "combined", from, to, deviceIds, groupIds); return combinedReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); } @@ -125,7 +125,7 @@ public class ReportResource extends SimpleObjectResource<Report> { @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); - LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds); + LogAction.report(getUserId(), false, "route", from, to, deviceIds, groupIds); return routeReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); } @@ -140,7 +140,7 @@ public class ReportResource extends SimpleObjectResource<Report> { @QueryParam("mail") boolean mail) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { - LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds); + LogAction.report(getUserId(), false, "route", from, to, deviceIds, groupIds); routeReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } @@ -166,7 +166,7 @@ public class ReportResource extends SimpleObjectResource<Report> { @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); - LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds); + LogAction.report(getUserId(), false, "events", from, to, deviceIds, groupIds); return eventsReportProvider.getObjects(getUserId(), deviceIds, groupIds, types, from, to); } @@ -182,7 +182,7 @@ public class ReportResource extends SimpleObjectResource<Report> { @QueryParam("mail") boolean mail) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { - LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds); + LogAction.report(getUserId(), false, "events", from, to, deviceIds, groupIds); eventsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, types, from, to); }); } @@ -209,7 +209,7 @@ public class ReportResource extends SimpleObjectResource<Report> { @QueryParam("to") Date to, @QueryParam("daily") boolean daily) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); - LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds); + LogAction.report(getUserId(), false, "summary", from, to, deviceIds, groupIds); return summaryReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to, daily); } @@ -225,7 +225,7 @@ public class ReportResource extends SimpleObjectResource<Report> { @QueryParam("mail") boolean mail) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { - LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds); + LogAction.report(getUserId(), false, "summary", from, to, deviceIds, groupIds); summaryReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to, daily); }); } @@ -251,7 +251,7 @@ public class ReportResource extends SimpleObjectResource<Report> { @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); - LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds); + LogAction.report(getUserId(), false, "trips", from, to, deviceIds, groupIds); return tripsReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); } @@ -266,7 +266,7 @@ public class ReportResource extends SimpleObjectResource<Report> { @QueryParam("mail") boolean mail) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { - LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds); + LogAction.report(getUserId(), false, "trips", from, to, deviceIds, groupIds); tripsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } @@ -291,7 +291,7 @@ public class ReportResource extends SimpleObjectResource<Report> { @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); - LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds); + LogAction.report(getUserId(), false, "stops", from, to, deviceIds, groupIds); return stopsReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); } @@ -306,7 +306,7 @@ public class ReportResource extends SimpleObjectResource<Report> { @QueryParam("mail") boolean mail) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { - LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds); + LogAction.report(getUserId(), false, "stops", from, to, deviceIds, groupIds); stopsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } @@ -326,7 +326,7 @@ public class ReportResource extends SimpleObjectResource<Report> { @Path("devices/{type:xlsx|mail}") @GET @Produces(EXCEL) - public Response geDevicesExcel( + public Response getDevicesExcel( @PathParam("type") String type) throws StorageException { permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), type.equals("mail"), stream -> { diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java index 2a72d2773..eb10ac763 100644 --- a/src/main/java/org/traccar/api/resource/ServerResource.java +++ b/src/main/java/org/traccar/api/resource/ServerResource.java @@ -161,4 +161,11 @@ public class ServerResource extends BaseResource { return cacheManager.toString(); } + @Path("reboot") + @POST + public void reboot() throws StorageException { + permissionsService.checkAdmin(getUserId()); + System.exit(130); + } + } diff --git a/src/main/java/org/traccar/api/security/LoginService.java b/src/main/java/org/traccar/api/security/LoginService.java index 930c4fa46..507288c31 100644 --- a/src/main/java/org/traccar/api/security/LoginService.java +++ b/src/main/java/org/traccar/api/security/LoginService.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2022 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import org.traccar.api.signature.TokenManager; import org.traccar.config.Config; import org.traccar.config.Keys; import org.traccar.database.LdapProvider; +import org.traccar.helper.DataConverter; import org.traccar.helper.model.UserUtil; import org.traccar.model.User; import org.traccar.storage.Storage; @@ -32,6 +33,7 @@ import jakarta.annotation.Nullable; import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; @Singleton @@ -58,6 +60,20 @@ public class LoginService { forceOpenId = config.getBoolean(Keys.OPENID_FORCE); } + public LoginResult login( + String scheme, String credentials) throws StorageException, GeneralSecurityException, IOException { + switch (scheme.toLowerCase()) { + case "bearer": + return login(credentials); + case "basic": + byte[] decodedBytes = DataConverter.parseBase64(credentials); + String[] auth = new String(decodedBytes, StandardCharsets.US_ASCII).split(":", 2); + return login(auth[0], auth[1], null); + default: + throw new SecurityException("Unsupported authorization scheme"); + } + } + public LoginResult login(String token) throws StorageException, GeneralSecurityException, IOException { if (serviceAccountToken != null && serviceAccountToken.equals(token)) { return new LoginResult(new ServiceAccountUser()); diff --git a/src/main/java/org/traccar/api/security/SecurityRequestFilter.java b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java index 12a5dbecf..07083e7a8 100644 --- a/src/main/java/org/traccar/api/security/SecurityRequestFilter.java +++ b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.traccar.api.resource.SessionResource; import org.traccar.database.StatisticsManager; -import org.traccar.helper.DataConverter; import org.traccar.model.User; import org.traccar.storage.StorageException; @@ -36,7 +35,6 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.SecurityContext; import java.io.IOException; import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.util.Date; @@ -44,15 +42,6 @@ public class SecurityRequestFilter implements ContainerRequestFilter { private static final Logger LOGGER = LoggerFactory.getLogger(SecurityRequestFilter.class); - public static String[] decodeBasicAuth(String auth) { - auth = auth.replaceFirst("[B|b]asic ", ""); - byte[] decodedBytes = DataConverter.parseBase64(auth); - if (decodedBytes != null && decodedBytes.length > 0) { - return new String(decodedBytes, StandardCharsets.US_ASCII).split(":", 2); - } - return null; - } - @Context private HttpServletRequest request; @@ -83,13 +72,8 @@ public class SecurityRequestFilter implements ContainerRequestFilter { if (authHeader != null) { try { - LoginResult loginResult; - if (authHeader.startsWith("Bearer ")) { - loginResult = loginService.login(authHeader.substring(7)); - } else { - String[] auth = decodeBasicAuth(authHeader); - loginResult = loginService.login(auth[0], auth[1], null); - } + String[] auth = authHeader.split(" "); + LoginResult loginResult = loginService.login(auth[0], auth[1]); if (loginResult != null) { User user = loginResult.getUser(); statisticsManager.registerRequest(user.getId()); diff --git a/src/main/java/org/traccar/config/Config.java b/src/main/java/org/traccar/config/Config.java index 47e1f0707..2d76a5c70 100644 --- a/src/main/java/org/traccar/config/Config.java +++ b/src/main/java/org/traccar/config/Config.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,20 +41,10 @@ public class Config { @Inject public Config(@Named("configFile") String file) throws IOException { try { - Properties mainProperties = new Properties(); try (InputStream inputStream = new FileInputStream(file)) { - mainProperties.loadFromXML(inputStream); + properties.loadFromXML(inputStream); } - String defaultConfigFile = mainProperties.getProperty("config.default"); - if (defaultConfigFile != null) { - try (InputStream inputStream = new FileInputStream(defaultConfigFile)) { - properties.loadFromXML(inputStream); - } - } - - properties.putAll(mainProperties); // override defaults - useEnvironmentVariables = Boolean.parseBoolean(System.getenv("CONFIG_USE_ENVIRONMENT_VARIABLES")) || Boolean.parseBoolean(properties.getProperty("config.useEnvironmentVariables")); @@ -102,7 +92,13 @@ public class Config { } public boolean getBoolean(ConfigKey<Boolean> key) { - return Boolean.parseBoolean(getString(key.getKey())); + String value = getString(key.getKey()); + if (value != null) { + return Boolean.parseBoolean(value); + } else { + Boolean defaultValue = key.getDefaultValue(); + return Objects.requireNonNullElse(defaultValue, false); + } } public int getInteger(ConfigKey<Integer> key) { diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java index d346084bd..91d5dac5d 100644 --- a/src/main/java/org/traccar/config/Keys.java +++ b/src/main/java/org/traccar/config/Keys.java @@ -23,7 +23,7 @@ public final class Keys { } /** - * Network interface for a the protocol. If not specified, server will bind all interfaces. + * Network interface for the protocol. If not specified, server will bind all interfaces. */ public static final ConfigSuffix<String> PROTOCOL_ADDRESS = new StringConfigSuffix( ".address", @@ -33,7 +33,7 @@ public final class Keys { * Port number for the protocol. Most protocols use TCP on the transport layer. Some protocols use UDP. Some * support both TCP and UDP. */ - public static final ConfigSuffix<Integer> PROTOCOL_PORT = new IntegerConfigSuffix( + public static final ConfigSuffix<Integer> PROTOCOL_PORT = new PortConfigSuffix( ".port", List.of(KeyType.CONFIG)); @@ -320,7 +320,8 @@ public final class Keys { */ public static final ConfigKey<String> SERVER_STATISTICS = new StringConfigKey( "server.statistics", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + "https://www.traccar.org/analytics/"); /** * Fuel drop threshold value. When fuel level drops from one position to another for more the value, an event is @@ -397,7 +398,8 @@ public final class Keys { */ public static final ConfigKey<Boolean> EVENT_IGNORE_DUPLICATE_ALERTS = new BooleanConfigKey( "event.ignoreDuplicateAlerts", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + true); /** * If set to true, invalid positions will be considered for motion logic. @@ -472,7 +474,8 @@ public final class Keys { */ public static final ConfigKey<String> DATABASE_CHANGELOG = new StringConfigKey( "database.changelog", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + "./schema/changelog-master.xml"); /** * Database connection pool size. Default value is defined by the HikariCP library. @@ -598,7 +601,7 @@ public final class Keys { "uid"); /** - * LDAP attribute used as user name. Default value is 'cn'. + * LDAP attribute used as username. Default value is 'cn'. */ public static final ConfigKey<String> LDAP_NAME_ATTRIBUTE = new StringConfigKey( "ldap.nameAttribute", @@ -671,7 +674,7 @@ public final class Keys { /** * OpenID Connect Authorization URL. * This can usually be found in the documentation of your identity provider or by using the well-known - * configuration endpoint, e.g. https://auth.example.com//.well-known/openid-configuration + * configuration endpoint, e.g. https://auth.example.com/.well-known/openid-configuration * Required to enable SSO if openid.issuerUrl is not set. */ public static final ConfigKey<String> OPENID_AUTH_URL = new StringConfigKey( @@ -736,7 +739,8 @@ public final class Keys { */ public static final ConfigKey<String> MEDIA_PATH = new StringConfigKey( "media.path", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + "./media"); /** * Optional parameter to specify network interface for web interface to bind to. By default server will bind to all @@ -747,12 +751,13 @@ public final class Keys { List.of(KeyType.CONFIG)); /** - * Web interface TCP port number. By default Traccar uses port 8082. To avoid specifying port in the browser you + * Web interface TCP port number. By default, Traccar uses port 8082. To avoid specifying port in the browser you * can set it to 80 (default HTTP port). */ public static final ConfigKey<Integer> WEB_PORT = new IntegerConfigKey( "web.port", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + 8082); /** * Maximum API requests per second. Above this limit requests and delayed and throttled. @@ -770,26 +775,20 @@ public final class Keys { 600); /** - * Sanitize all strings returned via API. This is needed to fix XSS issues in the old web interface. New React-based - * interface doesn't require this. - */ - public static final ConfigKey<Boolean> WEB_SANITIZE = new BooleanConfigKey( - "web.sanitize", - List.of(KeyType.CONFIG)); - - /** * Path to the web app folder. */ public static final ConfigKey<String> WEB_PATH = new StringConfigKey( "web.path", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + "./web"); /** * Path to a folder with overrides. It can be used for branding to keep custom logos in a separate place. */ public static final ConfigKey<String> WEB_OVERRIDE = new StringConfigKey( "web.override", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + "./override"); /** * WebSocket connection timeout in milliseconds. Default timeout is 5 minutes. @@ -1172,7 +1171,8 @@ public final class Keys { */ public static final ConfigKey<String> NOTIFICATOR_TYPES = new StringConfigKey( "notificator.types", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + "web,mail,command"); /** * If the event time is too old, we should not send notifications. This parameter is the threshold value in @@ -1261,6 +1261,13 @@ public final class Keys { List.of(KeyType.CONFIG)); /** + * Block notifications for specific users. The value should be a comma-separated list of internal user ids. + */ + public static final ConfigKey<String> NOTIFICATION_BLOCK_USERS = new StringConfigKey( + "notification.block.users", + List.of(KeyType.CONFIG)); + + /** * Maximum time period for reports in seconds. Can be useful to prevent users to request unreasonably long reports. * By default, there is no limit. */ @@ -1331,7 +1338,8 @@ public final class Keys { */ public static final ConfigKey<Boolean> FILTER_ENABLE = new BooleanConfigKey( "filter.enable", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + true); /** * Filter invalid (valid field is set to false) positions. @@ -1369,7 +1377,8 @@ public final class Keys { */ public static final ConfigKey<Long> FILTER_FUTURE = new LongConfigKey( "filter.future", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + 86400L); /** * Filter records with fix time in the past. The value is specified in seconds. Records that have fix time more @@ -1426,13 +1435,20 @@ public final class Keys { List.of(KeyType.CONFIG)); /** - * Filter position if the daily limit is exceeded for the device. + * Throttle positions if the daily limit is exceeded for the device. */ public static final ConfigKey<Integer> FILTER_DAILY_LIMIT = new IntegerConfigKey( "filter.dailyLimit", List.of(KeyType.CONFIG)); /** + * Throttling interval if the limit exceeded. The value is in seconds. + */ + public static final ConfigKey<Integer> FILTER_DAILY_LIMIT_INTERVAL = new IntegerConfigKey( + "filter.dailyLimitInterval", + List.of(KeyType.CONFIG)); + + /** * If false, the server expects all locations to come sequentially (for each device). Filter checks for duplicates, * distance, speed, or time period only against the location that was last received by server. * If true, the server expects locations to come at random order (since tracking device might go offline). @@ -1580,15 +1596,16 @@ public final class Keys { */ public static final ConfigKey<Boolean> GEOCODER_ENABLE = new BooleanConfigKey( "geocoder.enable", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + true); /** - * Reverse geocoder type. Check reverse geocoding documentation for more info. By default (if the value is not - * specified) server uses Google API. + * Reverse geocoder type. Check reverse geocoding documentation for more info. */ public static final ConfigKey<String> GEOCODER_TYPE = new StringConfigKey( "geocoder.type", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + "locationiq"); /** * Geocoder server URL. Applicable only to Nominatim and Gisgraphy providers. @@ -1602,7 +1619,8 @@ public final class Keys { */ public static final ConfigKey<String> GEOCODER_KEY = new StringConfigKey( "geocoder.key", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + "pk.689d849289c8c63708068b2ff1f63b2d"); /** * Language parameter for providers that support localization (e.g. Google and Nominatim). @@ -1630,7 +1648,8 @@ public final class Keys { */ public static final ConfigKey<Boolean> GEOCODER_IGNORE_POSITIONS = new BooleanConfigKey( "geocoder.ignorePositions", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + true); /** * Boolean flag to apply reverse geocoding to invalid positions. @@ -1652,7 +1671,8 @@ public final class Keys { */ public static final ConfigKey<Boolean> GEOCODER_ON_REQUEST = new BooleanConfigKey( "geocoder.onRequest", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + true); /** * Boolean flag to enable LBS location resolution. Some devices send cell towers information and WiFi point when GPS @@ -1853,7 +1873,8 @@ public final class Keys { */ public static final ConfigKey<String> LOGGER_FILE = new StringConfigKey( "logger.file", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + "./logs/tracker-server.log"); /** * Logging level. Default value is 'info'. @@ -1861,7 +1882,8 @@ public final class Keys { */ public static final ConfigKey<String> LOGGER_LEVEL = new StringConfigKey( "logger.level", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + "info"); /** * Print full exception traces. Useful for debugging. By default shortened traces are logged. @@ -1876,7 +1898,8 @@ public final class Keys { */ public static final ConfigKey<Boolean> LOGGER_ROTATE = new BooleanConfigKey( "logger.rotate", - List.of(KeyType.CONFIG)); + List.of(KeyType.CONFIG), + true); /** * Log file rotation interval, the default rotation interval is once a day. diff --git a/src/main/java/org/traccar/config/PortConfigSuffix.java b/src/main/java/org/traccar/config/PortConfigSuffix.java new file mode 100644 index 000000000..11d6f2dbb --- /dev/null +++ b/src/main/java/org/traccar/config/PortConfigSuffix.java @@ -0,0 +1,278 @@ +package org.traccar.config; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PortConfigSuffix extends ConfigSuffix<Integer> { + + private static final Map<String, Integer> PORTS = new HashMap<>(); + + static { + PORTS.put("gps103", 5001); + PORTS.put("tk103", 5002); + PORTS.put("gl100", 5003); + PORTS.put("gl200", 5004); + PORTS.put("t55", 5005); + PORTS.put("xexun", 5006); + PORTS.put("totem", 5007); + PORTS.put("enfora", 5008); + PORTS.put("meiligao", 5009); + PORTS.put("trv", 5010); + PORTS.put("suntech", 5011); + PORTS.put("progress", 5012); + PORTS.put("h02", 5013); + PORTS.put("jt600", 5014); + PORTS.put("huabao", 5015); + PORTS.put("v680", 5016); + PORTS.put("pt502", 5017); + PORTS.put("tr20", 5018); + PORTS.put("navis", 5019); + PORTS.put("meitrack", 5020); + PORTS.put("skypatrol", 5021); + PORTS.put("gt02", 5022); + PORTS.put("gt06", 5023); + PORTS.put("megastek", 5024); + PORTS.put("navigil", 5025); + PORTS.put("gpsgate", 5026); + PORTS.put("teltonika", 5027); + PORTS.put("mta6", 5028); + PORTS.put("tzone", 5029); + PORTS.put("tlt2h", 5030); + PORTS.put("taip", 5031); + PORTS.put("wondex", 5032); + PORTS.put("cellocator", 5033); + PORTS.put("galileo", 5034); + PORTS.put("ywt", 5035); + PORTS.put("tk102", 5036); + PORTS.put("intellitrac", 5037); + PORTS.put("gpsmta", 5038); + PORTS.put("wialon", 5039); + PORTS.put("carscop", 5040); + PORTS.put("apel", 5041); + PORTS.put("manpower", 5042); + PORTS.put("globalsat", 5043); + PORTS.put("atrack", 5044); + PORTS.put("pt3000", 5045); + PORTS.put("ruptela", 5046); + PORTS.put("topflytech", 5047); + PORTS.put("laipac", 5048); + PORTS.put("aplicom", 5049); + PORTS.put("gotop", 5050); + PORTS.put("sanav", 5051); + PORTS.put("gator", 5052); + PORTS.put("noran", 5053); + PORTS.put("m2m", 5054); + PORTS.put("osmand", 5055); + PORTS.put("easytrack", 5056); + PORTS.put("gpsmarker", 5057); + PORTS.put("khd", 5058); + PORTS.put("piligrim", 5059); + PORTS.put("stl060", 5060); + PORTS.put("cartrack", 5061); + PORTS.put("minifinder", 5062); + PORTS.put("haicom", 5063); + PORTS.put("eelink", 5064); + PORTS.put("box", 5065); + PORTS.put("freedom", 5066); + PORTS.put("telic", 5067); + PORTS.put("trackbox", 5068); + PORTS.put("visiontek", 5069); + PORTS.put("orion", 5070); + PORTS.put("riti", 5071); + PORTS.put("ulbotech", 5072); + PORTS.put("tramigo", 5073); + PORTS.put("tr900", 5074); + PORTS.put("ardi01", 5075); + PORTS.put("xt013", 5076); + PORTS.put("autofon", 5077); + PORTS.put("gosafe", 5078); + PORTS.put("tt8850", 5079); + PORTS.put("bce", 5080); + PORTS.put("xirgo", 5081); + PORTS.put("calamp", 5082); + PORTS.put("mtx", 5083); + PORTS.put("tytan", 5084); + PORTS.put("avl301", 5085); + PORTS.put("castel", 5086); + PORTS.put("mxt", 5087); + PORTS.put("cityeasy", 5088); + PORTS.put("aquila", 5089); + PORTS.put("flextrack", 5090); + PORTS.put("blackkite", 5091); + PORTS.put("adm", 5092); + PORTS.put("watch", 5093); + PORTS.put("t800x", 5094); + PORTS.put("upro", 5095); + PORTS.put("auro", 5096); + PORTS.put("disha", 5097); + PORTS.put("thinkrace", 5098); + PORTS.put("pathaway", 5099); + PORTS.put("arnavi", 5100); + PORTS.put("nvs", 5101); + PORTS.put("kenji", 5102); + PORTS.put("astra", 5103); + PORTS.put("homtecs", 5104); + PORTS.put("fox", 5105); + PORTS.put("gnx", 5106); + PORTS.put("arknav", 5107); + PORTS.put("supermate", 5108); + PORTS.put("appello", 5109); + PORTS.put("idpl", 5110); + PORTS.put("huasheng", 5111); + PORTS.put("l100", 5112); + PORTS.put("granit", 5113); + PORTS.put("carcell", 5114); + PORTS.put("obddongle", 5115); + PORTS.put("hunterpro", 5116); + PORTS.put("raveon", 5117); + PORTS.put("cradlepoint", 5118); + PORTS.put("arknavx8", 5119); + PORTS.put("autograde", 5120); + PORTS.put("oigo", 5121); + PORTS.put("jpkorjar", 5122); + PORTS.put("cguard", 5123); + PORTS.put("fifotrack", 5124); + PORTS.put("smokey", 5125); + PORTS.put("extremtrac", 5126); + PORTS.put("trakmate", 5127); + PORTS.put("at2000", 5128); + PORTS.put("maestro", 5129); + PORTS.put("ais", 5130); + PORTS.put("gt30", 5131); + PORTS.put("tmg", 5132); + PORTS.put("pretrace", 5133); + PORTS.put("pricol", 5134); + PORTS.put("siwi", 5135); + PORTS.put("starlink", 5136); + PORTS.put("dmt", 5137); + PORTS.put("xt2400", 5138); + PORTS.put("dmthttp", 5139); + PORTS.put("alematics", 5140); + PORTS.put("gps056", 5141); + PORTS.put("flexcomm", 5142); + PORTS.put("vt200", 5143); + PORTS.put("owntracks", 5144); + PORTS.put("vtfms", 5145); + PORTS.put("tlv", 5146); + PORTS.put("esky", 5147); + PORTS.put("genx", 5148); + PORTS.put("flespi", 5149); + PORTS.put("dway", 5150); + PORTS.put("recoda", 5151); + PORTS.put("oko", 5152); + PORTS.put("ivt401", 5153); + PORTS.put("sigfox", 5154); + PORTS.put("t57", 5155); + PORTS.put("spot", 5156); + PORTS.put("m2c", 5157); + PORTS.put("austinnb", 5158); + PORTS.put("opengts", 5159); + PORTS.put("cautela", 5160); + PORTS.put("continental", 5161); + PORTS.put("egts", 5162); + PORTS.put("robotrack", 5163); + PORTS.put("pt60", 5164); + PORTS.put("telemax", 5165); + PORTS.put("sabertek", 5166); + PORTS.put("retranslator", 5167); + PORTS.put("svias", 5168); + PORTS.put("eseal", 5169); + PORTS.put("freematics", 5170); + PORTS.put("avema", 5171); + PORTS.put("autotrack", 5172); + PORTS.put("tek", 5173); + PORTS.put("wristband", 5174); + PORTS.put("lacak", 5175); + PORTS.put("milesmate", 5176); + PORTS.put("anytrek", 5177); + PORTS.put("smartsole", 5178); + PORTS.put("its", 5179); + PORTS.put("xrb28", 5180); + PORTS.put("c2stek", 5181); + PORTS.put("nyitech", 5182); + PORTS.put("neos", 5183); + PORTS.put("satsol", 5184); + PORTS.put("globalstar", 5185); + PORTS.put("sanul", 5186); + PORTS.put("minifinder2", 5187); + PORTS.put("radar", 5188); + PORTS.put("techtlt", 5189); + PORTS.put("starcom", 5190); + PORTS.put("mictrack", 5191); + PORTS.put("plugin", 5192); + PORTS.put("leafspy", 5193); + PORTS.put("naviset", 5194); + PORTS.put("racedynamics", 5195); + PORTS.put("rst", 5196); + PORTS.put("pt215", 5197); + PORTS.put("pacifictrack", 5198); + PORTS.put("topin", 5199); + PORTS.put("outsafe", 5200); + PORTS.put("solarpowered", 5201); + PORTS.put("motor", 5202); + PORTS.put("omnicomm", 5203); + PORTS.put("s168", 5204); + PORTS.put("vnet", 5205); + PORTS.put("blue", 5206); + PORTS.put("pst", 5207); + PORTS.put("dingtek", 5208); + PORTS.put("wli", 5209); + PORTS.put("niot", 5210); + PORTS.put("portman", 5211); + PORTS.put("moovbox", 5212); + PORTS.put("futureway", 5213); + PORTS.put("polte", 5214); + PORTS.put("net", 5215); + PORTS.put("mobilogix", 5216); + PORTS.put("swiftech", 5217); + PORTS.put("iotm", 5218); + PORTS.put("dolphin", 5219); + PORTS.put("ennfu", 5220); + PORTS.put("navtelecom", 5221); + PORTS.put("startek", 5222); + PORTS.put("gs100", 5223); + PORTS.put("mavlink2", 5224); + PORTS.put("uux", 5225); + PORTS.put("r12w", 5226); + PORTS.put("flexiblereport", 5227); + PORTS.put("thinkpower", 5228); + PORTS.put("stb", 5229); + PORTS.put("b2316", 5230); + PORTS.put("hoopo", 5231); + PORTS.put("dualcam", 5232); + PORTS.put("xexun2", 5233); + PORTS.put("techtocruz", 5234); + PORTS.put("flexapi", 5235); + PORTS.put("dsf22", 5236); + PORTS.put("jido", 5237); + PORTS.put("armoli", 5238); + PORTS.put("teratrack", 5239); + PORTS.put("envotech", 5240); + PORTS.put("bstpl", 5241); + PORTS.put("thuraya", 5242); + PORTS.put("ndtpv6", 5243); + PORTS.put("g1rus", 5244); + PORTS.put("rftrack", 5245); + PORTS.put("vlt", 5246); + PORTS.put("transync", 5247); + PORTS.put("t622iridium", 5248); + PORTS.put("pui", 5249); + PORTS.put("nto", 5250); + PORTS.put("ramac", 5251); + PORTS.put("positrex", 5252); + PORTS.put("dragino", 5253); + PORTS.put("fleetguide", 5254); + PORTS.put("valtrack", 5255); + PORTS.put("snapper", 5256); + } + + PortConfigSuffix(String key, List<KeyType> types) { + super(key, types, null); + } + + @Override + public ConfigKey<Integer> withPrefix(String protocol) { + return new IntegerConfigKey(protocol + keySuffix, types, PORTS.get(protocol)); + } +} diff --git a/src/main/java/org/traccar/database/NotificationManager.java b/src/main/java/org/traccar/database/NotificationManager.java index 65437f0a1..60b4f246b 100644 --- a/src/main/java/org/traccar/database/NotificationManager.java +++ b/src/main/java/org/traccar/database/NotificationManager.java @@ -42,8 +42,10 @@ import jakarta.annotation.Nullable; import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.util.Arrays; +import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.stream.Collectors; @Singleton @@ -59,6 +61,7 @@ public class NotificationManager { private final boolean geocodeOnRequest; private final long timeThreshold; + private final Set<Long> blockedUsers = new HashSet<>(); @Inject public NotificationManager( @@ -71,6 +74,12 @@ public class NotificationManager { this.geocoder = geocoder; geocodeOnRequest = config.getBoolean(Keys.GEOCODER_ON_REQUEST); timeThreshold = config.getLong(Keys.NOTIFICATOR_TIME_THRESHOLD); + String blockedUsersString = config.getString(Keys.NOTIFICATION_BLOCK_USERS); + if (blockedUsersString != null) { + for (String userIdString : blockedUsersString.split(",")) { + blockedUsers.add(Long.parseLong(userIdString)); + } + } } private void updateEvent(Event event, Position position) { @@ -122,6 +131,10 @@ public class NotificationManager { notifications.forEach(notification -> { cacheManager.getNotificationUsers(notification.getId(), event.getDeviceId()).forEach(user -> { + if (blockedUsers.contains(user.getId())) { + LOGGER.info("User {} notification blocked", user.getId()); + return; + } for (String notificator : notification.getNotificatorsTypes()) { try { notificatorManager.getNotificator(notificator).send(notification, user, event, position); diff --git a/src/main/java/org/traccar/database/StatisticsManager.java b/src/main/java/org/traccar/database/StatisticsManager.java index 445e53e7c..8711289a0 100644 --- a/src/main/java/org/traccar/database/StatisticsManager.java +++ b/src/main/java/org/traccar/database/StatisticsManager.java @@ -121,7 +121,7 @@ public class StatisticsManager { } String url = config.getString(Keys.SERVER_STATISTICS); - if (url != null) { + if (url != null && !url.isEmpty()) { String time = DateUtil.formatDate(statistics.getCaptureTime()); Form form = new Form(); diff --git a/src/main/java/org/traccar/geocoder/TestGeocoder.java b/src/main/java/org/traccar/geocoder/PlusCodesGeocoder.java index 259f13c6c..5d003192f 100644 --- a/src/main/java/org/traccar/geocoder/TestGeocoder.java +++ b/src/main/java/org/traccar/geocoder/PlusCodesGeocoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2022 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,10 @@ */ package org.traccar.geocoder; +import com.google.openlocationcode.OpenLocationCode; import org.traccar.database.StatisticsManager; -public class TestGeocoder implements Geocoder { +public class PlusCodesGeocoder implements Geocoder { @Override public void setStatisticsManager(StatisticsManager statisticsManager) { @@ -25,7 +26,7 @@ public class TestGeocoder implements Geocoder { @Override public String getAddress(double latitude, double longitude, ReverseGeocoderCallback callback) { - String address = String.format("Location %f, %f", latitude, longitude); + String address = new OpenLocationCode(latitude, longitude).getCode(); if (callback != null) { callback.onSuccess(address); return null; diff --git a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java index 4293bd1fc..d286866a5 100644 --- a/src/main/java/org/traccar/handler/ComputedAttributesHandler.java +++ b/src/main/java/org/traccar/handler/ComputedAttributesHandler.java @@ -35,12 +35,14 @@ import org.traccar.session.cache.CacheManager; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Date; +import java.util.stream.Collectors; public class ComputedAttributesHandler extends BasePositionHandler { @@ -63,7 +65,7 @@ public class ComputedAttributesHandler extends BasePositionHandler { sandbox.allow(Math.class.getName()); List.of( Double.class, Float.class, Integer.class, Long.class, Short.class, - Character.class, Boolean.class, String.class, Byte.class) + Character.class, Boolean.class, String.class, Byte.class, Date.class) .forEach((type) -> sandbox.allow(type.getName())); features = new JexlFeatures() .localVar(config.getBoolean(Keys.PROCESSING_COMPUTED_ATTRIBUTES_LOCAL_VARIABLES)) @@ -139,7 +141,9 @@ public class ComputedAttributesHandler extends BasePositionHandler { @Override public void handlePosition(Position position, Callback callback) { - Collection<Attribute> attributes = cacheManager.getDeviceObjects(position.getDeviceId(), Attribute.class); + var attributes = cacheManager.getDeviceObjects(position.getDeviceId(), Attribute.class).stream() + .sorted(Comparator.comparing(Attribute::getPriority).reversed()) + .collect(Collectors.toUnmodifiableList()); for (Attribute attribute : attributes) { if (attribute.getAttribute() != null) { Object result = null; diff --git a/src/main/java/org/traccar/handler/DatabaseHandler.java b/src/main/java/org/traccar/handler/DatabaseHandler.java index 5d96ebb34..4619e9d34 100644 --- a/src/main/java/org/traccar/handler/DatabaseHandler.java +++ b/src/main/java/org/traccar/handler/DatabaseHandler.java @@ -42,7 +42,7 @@ public class DatabaseHandler extends BasePositionHandler { try { position.setId(storage.addObject(position, new Request(new Columns.Exclude("id")))); - statisticsManager.messageStoredCount(position.getDeviceId()); + statisticsManager.registerMessageStored(position.getDeviceId(), position.getProtocol()); } catch (Exception error) { LOGGER.warn("Failed to store position", error); } diff --git a/src/main/java/org/traccar/handler/FilterHandler.java b/src/main/java/org/traccar/handler/FilterHandler.java index 796c302fb..700fdbc13 100644 --- a/src/main/java/org/traccar/handler/FilterHandler.java +++ b/src/main/java/org/traccar/handler/FilterHandler.java @@ -53,6 +53,7 @@ public class FilterHandler extends BasePositionHandler { private final int filterMaxSpeed; private final long filterMinPeriod; private final int filterDailyLimit; + private final long filterDailyLimitInterval; private final boolean filterRelative; private final long skipLimit; private final boolean skipAttributes; @@ -77,6 +78,7 @@ public class FilterHandler extends BasePositionHandler { filterMaxSpeed = config.getInteger(Keys.FILTER_MAX_SPEED); filterMinPeriod = config.getInteger(Keys.FILTER_MIN_PERIOD) * 1000L; filterDailyLimit = config.getInteger(Keys.FILTER_DAILY_LIMIT); + filterDailyLimitInterval = config.getInteger(Keys.FILTER_DAILY_LIMIT_INTERVAL) * 1000L; filterRelative = config.getBoolean(Keys.FILTER_RELATIVE); skipLimit = config.getLong(Keys.FILTER_SKIP_LIMIT) * 1000; skipAttributes = config.getBoolean(Keys.FILTER_SKIP_ATTRIBUTES_ENABLE); @@ -151,7 +153,7 @@ public class FilterHandler extends BasePositionHandler { if (filterMaxSpeed != 0 && last != null) { double distance = position.getDouble(Position.KEY_DISTANCE); double time = position.getFixTime().getTime() - last.getFixTime().getTime(); - return UnitsConverter.knotsFromMps(distance / (time / 1000)) > filterMaxSpeed; + return time > 0 && UnitsConverter.knotsFromMps(distance / (time / 1000)) > filterMaxSpeed; } return false; } @@ -164,9 +166,12 @@ public class FilterHandler extends BasePositionHandler { return false; } - private boolean filterDailyLimit(Position position) { - if (filterDailyLimit != 0) { - return statisticsManager.messageStoredCount(position.getDeviceId()) >= filterDailyLimit; + private boolean filterDailyLimit(Position position, Position last) { + if (filterDailyLimit != 0 + && statisticsManager.messageStoredCount(position.getDeviceId()) >= filterDailyLimit) { + long lastTime = last != null ? last.getFixTime().getTime() : 0; + long interval = position.getFixTime().getTime() - lastTime; + return filterDailyLimitInterval <= 0 || interval < filterDailyLimitInterval; } return false; } @@ -216,20 +221,18 @@ public class FilterHandler extends BasePositionHandler { if (filterApproximate(position)) { filterType.append("Approximate "); } - if (filterDailyLimit(position)) { - filterType.append("DailyLimit "); - } // filter out excessive data long deviceId = position.getDeviceId(); - if (filterDuplicate || filterStatic || filterDistance > 0 || filterMaxSpeed > 0 || filterMinPeriod > 0) { - Position preceding = null; + if (filterDuplicate || filterStatic + || filterDistance > 0 || filterMaxSpeed > 0 || filterMinPeriod > 0 || filterDailyLimit > 0) { + Position preceding; if (filterRelative) { try { Date newFixTime = position.getFixTime(); preceding = getPrecedingPosition(deviceId, newFixTime); } catch (StorageException e) { - LOGGER.warn("Error retrieving preceding position; fallbacking to last received position.", e); + LOGGER.warn("Error retrieving preceding position; fall backing to last received position.", e); preceding = cacheManager.getPosition(deviceId); } } else { @@ -250,6 +253,9 @@ public class FilterHandler extends BasePositionHandler { if (filterMinPeriod(position, preceding)) { filterType.append("MinPeriod "); } + if (filterDailyLimit(position, preceding)) { + filterType.append("DailyLimit "); + } } Device device = cacheManager.getObject(Device.class, deviceId); diff --git a/src/main/java/org/traccar/handler/GeocoderHandler.java b/src/main/java/org/traccar/handler/GeocoderHandler.java index b84237856..3744700da 100644 --- a/src/main/java/org/traccar/handler/GeocoderHandler.java +++ b/src/main/java/org/traccar/handler/GeocoderHandler.java @@ -43,11 +43,7 @@ public class GeocoderHandler extends BasePositionHandler { @Override public void handlePosition(Position position, Callback callback) { - if (!ignorePositions) { - callback.processed(false); - } - - if (processInvalidPositions || position.getValid()) { + if (!ignorePositions && (processInvalidPositions || position.getValid())) { if (reuseDistance != 0) { Position lastPosition = cacheManager.getPosition(position.getDeviceId()); if (lastPosition != null && lastPosition.getAddress() != null diff --git a/src/main/java/org/traccar/helper/LogAction.java b/src/main/java/org/traccar/helper/LogAction.java index b255b9206..3c4b69a2e 100644 --- a/src/main/java/org/traccar/helper/LogAction.java +++ b/src/main/java/org/traccar/helper/LogAction.java @@ -43,14 +43,17 @@ public final class LogAction { private static final String ACTION_LOGIN = "login"; private static final String ACTION_LOGOUT = "logout"; - private static final String ACTION_DEVICE_ACCUMULATORS = "resetDeviceAccumulators"; + private static final String ACTION_ACCUMULATORS = "accumulators"; + private static final String ACTION_COMMAND = "command"; private static final String PATTERN_OBJECT = "user: %d, action: %s, object: %s, id: %d"; private static final String PATTERN_LINK = "user: %d, action: %s, owner: %s, id: %d, property: %s, id: %d"; private static final String PATTERN_LOGIN = "user: %d, action: %s, from: %s"; private static final String PATTERN_LOGIN_FAILED = "login failed from: %s"; - private static final String PATTERN_DEVICE_ACCUMULATORS = "user: %d, action: %s, deviceId: %d"; - private static final String PATTERN_REPORT = "user: %d, report: %s, from: %s, to: %s, devices: %s, groups: %s"; + private static final String PATTERN_ACCUMULATORS = "user: %d, action: %s, deviceId: %d"; + private static final String PATTERN_COMMAND_DEVICE = "user: %d, action: %s, deviceId: %d, type: %s"; + private static final String PATTERN_COMMAND_GROUP = "user: %d, action: %s, groupId: %d, type: %s"; + private static final String PATTERN_REPORT = "user: %d, %s: %s, from: %s, to: %s, devices: %s, groups: %s"; public static void create(long userId, BaseModel object) { logObjectAction(ACTION_CREATE, userId, object.getClass(), object.getId()); @@ -87,9 +90,27 @@ public final class LogAction { LOGGER.info(String.format(PATTERN_LOGIN_FAILED, remoteAddress)); } - public static void resetDeviceAccumulators(long userId, long deviceId) { + public static void resetAccumulators(long userId, long deviceId) { LOGGER.info(String.format( - PATTERN_DEVICE_ACCUMULATORS, userId, ACTION_DEVICE_ACCUMULATORS, deviceId)); + PATTERN_ACCUMULATORS, userId, ACTION_ACCUMULATORS, deviceId)); + } + + public static void command(long userId, long groupId, long deviceId, String type) { + if (groupId > 0) { + LOGGER.info(String.format(PATTERN_COMMAND_GROUP, userId, ACTION_COMMAND, groupId, type)); + } else { + LOGGER.info(String.format(PATTERN_COMMAND_DEVICE, userId, ACTION_COMMAND, deviceId, type)); + } + } + + public static void report( + long userId, boolean scheduled, String report, + Date from, Date to, List<Long> deviceIds, List<Long> groupIds) { + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + LOGGER.info(String.format( + PATTERN_REPORT, userId, scheduled ? "scheduled" : "report", report, + dateFormat.format(from), dateFormat.format(to), + deviceIds.toString(), groupIds.toString())); } private static void logObjectAction(String action, long userId, Class<?> clazz, long objectId) { @@ -112,13 +133,4 @@ public final class LogAction { LOGGER.info(String.format(PATTERN_LOGIN, userId, action, remoteAddress)); } - public static void logReport( - long userId, String report, Date from, Date to, List<Long> deviceIds, List<Long> groupIds) { - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); - LOGGER.info(String.format( - PATTERN_REPORT, userId, report, - dateFormat.format(from), dateFormat.format(to), - deviceIds.toString(), groupIds.toString())); - } - } diff --git a/src/main/java/org/traccar/helper/SanitizerModule.java b/src/main/java/org/traccar/helper/SanitizerModule.java deleted file mode 100644 index af9ac5c2b..000000000 --- a/src/main/java/org/traccar/helper/SanitizerModule.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2018 Anton Tananaev (anton@traccar.org) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.traccar.helper; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import org.owasp.encoder.Encode; - -import java.io.IOException; - -public class SanitizerModule extends SimpleModule { - - public static class SanitizerSerializer extends StdSerializer<String> { - - protected SanitizerSerializer() { - super(String.class); - } - - @Override - public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException { - gen.writeString(Encode.forHtml(value)); - } - - } - - public SanitizerModule() { - addSerializer(new SanitizerSerializer()); - } - -} diff --git a/src/main/java/org/traccar/model/Attribute.java b/src/main/java/org/traccar/model/Attribute.java index 65f2e3881..573207170 100644 --- a/src/main/java/org/traccar/model/Attribute.java +++ b/src/main/java/org/traccar/model/Attribute.java @@ -61,4 +61,14 @@ public class Attribute extends BaseModel { this.type = type; } + private int priority; + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + } diff --git a/src/main/java/org/traccar/model/Calendar.java b/src/main/java/org/traccar/model/Calendar.java index 03f1995ba..76c9a2cc1 100644 --- a/src/main/java/org/traccar/model/Calendar.java +++ b/src/main/java/org/traccar/model/Calendar.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,6 +34,7 @@ import java.time.Duration; import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; @StorageName("tc_calendars") @@ -78,10 +79,9 @@ public class Calendar extends ExtendedModel { } } - public Collection<Period> findPeriods(Date date) { - var calendarDate = new net.fortuna.ical4j.model.Date(date); + public Set<Period> findPeriods(Date date) { return findEvents(date).stream() - .flatMap((event) -> event.getConsumedTime(calendarDate, calendarDate).stream()) + .flatMap((e) -> e.calculateRecurrenceSet(new Period(new DateTime(date), Duration.ZERO)).stream()) .collect(Collectors.toSet()); } diff --git a/src/main/java/org/traccar/model/Server.java b/src/main/java/org/traccar/model/Server.java index 6442186b6..b29fe27cc 100644 --- a/src/main/java/org/traccar/model/Server.java +++ b/src/main/java/org/traccar/model/Server.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -126,16 +126,6 @@ public class Server extends ExtendedModel implements UserRestrictions { this.zoom = zoom; } - private boolean twelveHourFormat; - - public boolean getTwelveHourFormat() { - return twelveHourFormat; - } - - public void setTwelveHourFormat(boolean twelveHourFormat) { - this.twelveHourFormat = twelveHourFormat; - } - private boolean forceSettings; public boolean getForceSettings() { diff --git a/src/main/java/org/traccar/model/User.java b/src/main/java/org/traccar/model/User.java index 8cfee0f48..9b8ee3e53 100644 --- a/src/main/java/org/traccar/model/User.java +++ b/src/main/java/org/traccar/model/User.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -133,16 +133,6 @@ public class User extends ExtendedModel implements UserRestrictions, Disableable this.zoom = zoom; } - private boolean twelveHourFormat; - - public boolean getTwelveHourFormat() { - return twelveHourFormat; - } - - public void setTwelveHourFormat(boolean twelveHourFormat) { - this.twelveHourFormat = twelveHourFormat; - } - private String coordinateFormat; public String getCoordinateFormat() { diff --git a/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java b/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java index b10ff4c64..ff192807b 100644 --- a/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/EasyTrackProtocolDecoder.java @@ -92,7 +92,7 @@ public class EasyTrackProtocolDecoder extends BaseProtocolDecoder { .number("C(d+);") // coolant temperature .number("L(d+.d);") // fuel level .number("[XY][MH]d+.d+;") - .number("M(d+);") // mileage + .number("Md+.?d*;") // mileage .number("F(d+.d+);") // fuel consumption .number("T(d+);") // engine time .any() @@ -264,7 +264,6 @@ public class EasyTrackProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ENGINE_LOAD, parser.nextDouble()); position.set(Position.KEY_COOLANT_TEMP, parser.nextInt()); position.set(Position.KEY_FUEL_LEVEL, parser.nextDouble()); - position.set(Position.KEY_ODOMETER, parser.nextInt()); position.set(Position.KEY_FUEL_CONSUMPTION, parser.nextDouble()); position.set(Position.KEY_HOURS, parser.nextInt()); diff --git a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java index 775e98401..8edce9346 100644 --- a/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gl200TextProtocolDecoder.java @@ -974,6 +974,9 @@ public class Gl200TextProtocolDecoder extends BaseProtocolDecoder { if (model.startsWith("GV") && !model.startsWith("GV6")) { position.set(Position.PREFIX_ADC + 2, v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1]) * 0.001); } + if (model.startsWith("GV355CEU")) { + index += 1; // reserved + } position.set(Position.KEY_BATTERY_LEVEL, v[index++].isEmpty() ? null : Integer.parseInt(v[index - 1])); if (model.startsWith("GL5")) { diff --git a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java index b75e612b8..654071d22 100644 --- a/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/GlobalstarProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import org.traccar.BaseHttpProtocolDecoder; -import org.traccar.config.Keys; import org.traccar.session.DeviceSession; import org.traccar.NetworkMessage; import org.traccar.Protocol; @@ -65,12 +64,6 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder { private final XPath xPath; private final XPathExpression messageExpression; - private boolean alternative; - - public void setAlternative(boolean alternative) { - this.alternative = alternative; - } - public GlobalstarProtocolDecoder(Protocol protocol) { super(protocol); try { @@ -89,11 +82,6 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder { } } - @Override - protected void init() { - this.alternative = getConfig().getBoolean(Keys.PROTOCOL_ALTERNATIVE.withPrefix(getProtocolName())); - } - private void sendResponse(Channel channel, String messageId) throws TransformerException { Document document = documentBuilder.newDocument(); @@ -144,6 +132,7 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder { DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, xPath.evaluate("esn", node)); if (deviceSession != null) { + boolean atlas = "AtlasTrax".equalsIgnoreCase(getDeviceModel(deviceSession)); Position position = new Position(getProtocolName()); position.setDeviceId(deviceSession.getDeviceId()); @@ -154,7 +143,7 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder { int flags = buf.readUnsignedByte(); int type; - if (alternative) { + if (atlas) { type = BitUtil.to(flags, 1); position.setValid(true); position.set(Position.PREFIX_IN + 1, !BitUtil.check(flags, 1)); @@ -179,7 +168,7 @@ public class GlobalstarProtocolDecoder extends BaseHttpProtocolDecoder { position.setLongitude(longitude > 180 ? longitude - 360 : longitude); int speed = 0; - if (alternative) { + if (atlas) { speed = buf.readUnsignedByte(); position.setSpeed(UnitsConverter.knotsFromKph(speed)); position.set("batteryReplace", BitUtil.check(buf.readUnsignedByte(), 7)); diff --git a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java index 6c0380278..a1a9c3773 100644 --- a/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/Gt06ProtocolDecoder.java @@ -410,7 +410,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { } } - private String decodeAlarm(short value) { + private String decodeAlarm(short value, String model) { + boolean modelLW = model != null && model.toUpperCase().startsWith("LW"); switch (value) { case 0x01: return Position.ALARM_SOS; @@ -427,7 +428,6 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { return Position.ALARM_OVERSPEED; case 0x0E: case 0x0F: - case 0x19: return Position.ALARM_LOW_BATTERY; case 0x11: return Position.ALARM_POWER_OFF; @@ -438,17 +438,21 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { case 0x14: return Position.ALARM_DOOR; case 0x18: - return Position.ALARM_REMOVING; - case 0x23: - return Position.ALARM_FALL_DOWN; + return modelLW ? Position.ALARM_ACCIDENT : Position.ALARM_REMOVING; + case 0x19: + return modelLW ? Position.ALARM_ACCELERATION : Position.ALARM_LOW_BATTERY; + case 0x1A: case 0x28: return Position.ALARM_BRAKING; - case 0x29: - return Position.ALARM_ACCELERATION; + case 0x1B: case 0x2A: case 0x2B: case 0x2E: return Position.ALARM_CORNERING; + case 0x23: + return Position.ALARM_FALL_DOWN; + case 0x29: + return Position.ALARM_ACCELERATION; case 0x2C: return Position.ALARM_ACCIDENT; case 0x30: @@ -831,7 +835,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { int satellites = BitUtil.between(signal, 10, 15) + BitUtil.between(signal, 5, 10); position.set(Position.KEY_SATELLITES, satellites); position.set(Position.KEY_RSSI, BitUtil.to(signal, 5)); - position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + position.set(Position.KEY_ALARM, decodeAlarm( + buf.readUnsignedByte(), getDeviceModel(deviceSession))); buf.readUnsignedByte(); // language position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte()); buf.readUnsignedByte(); // working mode @@ -842,7 +847,7 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_RSSI, buf.readUnsignedByte()); short alarmExtension = buf.readUnsignedByte(); if (variant != Variant.VXT01) { - position.set(Position.KEY_ALARM, decodeAlarm(alarmExtension)); + position.set(Position.KEY_ALARM, decodeAlarm(alarmExtension, getDeviceModel(deviceSession))); } } } @@ -881,7 +886,8 @@ public class Gt06ProtocolDecoder extends BaseProtocolDecoder { decodeStatus(position, buf); position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); position.set(Position.KEY_RSSI, buf.readUnsignedByte()); - position.set(Position.KEY_ALARM, decodeAlarm(buf.readUnsignedByte())); + position.set(Position.KEY_ALARM, decodeAlarm( + buf.readUnsignedByte(), getDeviceModel(deviceSession))); position.set("oil", buf.readUnsignedShort()); int temperature = buf.readUnsignedByte(); if (BitUtil.check(temperature, 7)) { diff --git a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java index 648c5fb42..d010a8fe0 100644 --- a/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/HuabaoProtocolDecoder.java @@ -492,6 +492,13 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_BATTERY, Integer.parseInt(lockStatus.substring(2, 5)) * 0.01); } break; + case 0x51: + if (length == 16) { + for (int i = 1; i <= 8; i++) { + position.set(Position.PREFIX_TEMP + i, buf.readShort()); + } + } + break; case 0x56: position.set(Position.KEY_BATTERY_LEVEL, buf.readUnsignedByte() * 10); buf.readUnsignedByte(); // reserved @@ -668,6 +675,10 @@ public class HuabaoProtocolDecoder extends BaseProtocolDecoder { position.set("fuel2", Double.parseDouble( buf.readCharSequence(6, StandardCharsets.US_ASCII).toString())); break; + case 0x00B2: + position.set(Position.KEY_ICCID, ByteBufUtil.hexDump( + buf.readSlice(10)).replaceAll("f", "")); + break; case 0x00CE: position.set(Position.KEY_POWER, buf.readUnsignedShort() * 0.01); break; diff --git a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java index e88b34478..2b234ab21 100644 --- a/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/KhdProtocolDecoder.java @@ -171,7 +171,7 @@ public class KhdProtocolDecoder extends BaseProtocolDecoder { } long status = buf.readUnsignedInt(); - position.set(Position.KEY_IGNITION, BitUtil.check(status, 7 + 3 * 8)); + position.set(Position.KEY_IGNITION, !BitUtil.check(status, 7 + 3 * 8)); position.set(Position.KEY_STATUS, status); buf.readUnsignedShort(); diff --git a/src/main/java/org/traccar/protocol/SnapperFrameDecoder.java b/src/main/java/org/traccar/protocol/SnapperFrameDecoder.java new file mode 100644 index 000000000..be45346a6 --- /dev/null +++ b/src/main/java/org/traccar/protocol/SnapperFrameDecoder.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.traccar.BaseFrameDecoder; + +public class SnapperFrameDecoder extends BaseFrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ByteBuf buf) throws Exception { + + byte header = buf.getByte(buf.readerIndex()); + if (header == 'P') { + if (buf.readableBytes() >= 2) { + return buf.readRetainedSlice(2); + } + } else if (buf.readableBytes() >= 16) { + int length = buf.getIntLE(buf.readerIndex() + 12) + 12 + 4 + 9; + if (buf.readableBytes() >= length) { + return buf.readRetainedSlice(length); + } + } + + return null; + } + +} diff --git a/src/main/java/org/traccar/protocol/SnapperProtocol.java b/src/main/java/org/traccar/protocol/SnapperProtocol.java new file mode 100644 index 000000000..25a11ed3a --- /dev/null +++ b/src/main/java/org/traccar/protocol/SnapperProtocol.java @@ -0,0 +1,37 @@ +/* + * Copyright 2024 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import jakarta.inject.Inject; +import org.traccar.BaseProtocol; +import org.traccar.PipelineBuilder; +import org.traccar.TrackerServer; +import org.traccar.config.Config; + +public class SnapperProtocol extends BaseProtocol { + + @Inject + public SnapperProtocol(Config config) { + addServer(new TrackerServer(config, getName(), false) { + @Override + protected void addProtocolHandlers(PipelineBuilder pipeline, Config config) { + pipeline.addLast(new SnapperFrameDecoder()); + pipeline.addLast(new SnapperProtocolDecoder(SnapperProtocol.this)); + } + }); + } + +} diff --git a/src/main/java/org/traccar/protocol/SnapperProtocolDecoder.java b/src/main/java/org/traccar/protocol/SnapperProtocolDecoder.java new file mode 100644 index 000000000..ef1a4426a --- /dev/null +++ b/src/main/java/org/traccar/protocol/SnapperProtocolDecoder.java @@ -0,0 +1,229 @@ +/* + * Copyright 2024 Anton Tananaev (anton@traccar.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.traccar.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import org.traccar.BaseProtocolDecoder; +import org.traccar.NetworkMessage; +import org.traccar.Protocol; +import org.traccar.helper.BitUtil; +import org.traccar.helper.Checksum; +import org.traccar.model.Position; +import org.traccar.session.DeviceSession; + +import java.io.StringReader; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +public class SnapperProtocolDecoder extends BaseProtocolDecoder { + + public SnapperProtocolDecoder(Protocol protocol) { + super(protocol); + } + + public static final int MSG_HELLO = 0x01; + public static final int MSG_SENDING_START = 0x02; + public static final int MSG_SENDING_FINISH = 0x03; + public static final int MSG_SEND_EVENTS = 0x21; + public static final int MSG_SEND_TECH_INFO = 0x23; + public static final int MSG_UPDATE_CMS_NUM = 0x24; + public static final int MSG_SEND_SYSTEM_INFO = 0x26; + public static final int MSG_SEND_USER_PHONE_NUMBERS = 0x31; + public static final int MSG_SEND_GPS_DATA = 0x32; + public static final int MSG_SEND_LBS_DATA = 0x33; + public static final int MSG_SEND_SYSTEM_STATUS = 0x34; + public static final int MSG_SEND_TRANSIT_SETTINGS = 0x35; + public static final int MSG_GET_SETTINGS = 0x36; + public static final int MSG_SEND_CONCATENATED_PACKET = 0x37; + public static final int MSG_SEND_DEBUG_INFO = 0x38; + + private void sendResponse( + Channel channel, SocketAddress remoteAddress, int index, int type, String answer) { + + if (channel != null) { + ByteBuf response = Unpooled.buffer(); + response.writeByte('K'); + response.writeByte(3); // protocol version + response.writeLongLE(0); // reserved + response.writeShortLE(0); // encryption + response.writeIntLE(answer.length()); + response.writeIntLE(0); // reserved + response.writeShortLE(index); + response.writeByte(Checksum.sum(ByteBuffer.wrap(answer.getBytes(StandardCharsets.US_ASCII)))); + response.writeShortLE(type); + response.writeCharSequence(answer, StandardCharsets.US_ASCII); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + } + + private void decodeEvents(Position position, ByteBuf buf) { + + position.set(Position.KEY_EVENT, buf.readUnsignedByte()); + buf.readUnsignedByte(); // info 1 + buf.readUnsignedByte(); // info 2 + buf.readUnsignedIntLE(); // timestamp + buf.readUnsignedByte(); // timezone + } + + private void decodeTechInfo(Position position, ByteBuf buf) { + + for (int i = 0; i < 5; i++) { + buf.readUnsignedByte(); // index + int type = buf.readUnsignedByte(); + switch (type) { + case 0x00: + position.set(Position.KEY_POWER, buf.readUnsignedByte() * 0.1); + position.set(Position.KEY_DEVICE_TEMP, buf.readByte()); + position.set(Position.KEY_RSSI, buf.readUnsignedByte()); + break; + case 0x01: + position.set("interiorTemp", buf.readByte()); + position.set("engineTemp", buf.readByte()); + buf.readUnsignedByte(); // reserved + break; + default: + buf.skipBytes(3); + break; + } + } + } + + private void decodeGpsData(Position position, ByteBuf buf) throws ParseException { + + String content = buf.readCharSequence(buf.readableBytes(), StandardCharsets.US_ASCII).toString(); + JsonObject json = Json.createReader(new StringReader(content)).readObject(); + + int flags = Integer.parseInt(json.getString("f"), 16); + if (!BitUtil.check(flags, 3)) { + return; + } + + position.setValid(BitUtil.check(flags, 1)); + if (!BitUtil.check(flags, 6)) { + position.setLatitude(-position.getLatitude()); + } + if (!BitUtil.check(flags, 7)) { + position.setLongitude(-position.getLongitude()); + } + + DateFormat dateFormat = new SimpleDateFormat("ddMMyyHHmmss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + position.setTime(dateFormat.parse(json.getString("d") + json.getString("t").split("\\.")[0])); + + String lat = json.getString("la"); + position.setLatitude(Integer.parseInt(lat.substring(0, 2)) + Double.parseDouble(lat.substring(2)) / 60); + String lon = json.getString("lo"); + position.setLongitude(Integer.parseInt(lon.substring(0, 3)) + Double.parseDouble(lon.substring(3)) / 60); + + position.setAltitude(Double.parseDouble(json.getString("a"))); + position.setSpeed(Double.parseDouble(json.getString("s"))); + position.setCourse(Double.parseDouble(json.getString("c"))); + + position.set(Position.KEY_SATELLITES, Integer.parseInt(json.getString("sv"))); + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ByteBuf buf = (ByteBuf) msg; + + byte header = buf.readByte(); + if (header == 'P') { + if (channel != null) { + ByteBuf response = Unpooled.wrappedBuffer(new byte[] {0x50, 0x4f}); + channel.writeAndFlush(new NetworkMessage(response, remoteAddress)); + } + return null; + } + + buf.readUnsignedByte(); // protocol version + buf.readUnsignedIntLE(); // system bonus identifier + + String serialNumber = String.valueOf(buf.readUnsignedIntLE()); + DeviceSession deviceSession = getDeviceSession(channel, remoteAddress, serialNumber); + if (deviceSession == null) { + return null; + } + + buf.readUnsignedShortLE(); // encryption + int length = buf.readIntLE(); + buf.readUnsignedByte(); // flags + buf.readUnsignedMediumLE(); // reserved + int index = buf.readUnsignedShortLE(); + buf.readUnsignedByte(); // checksum + int type = buf.readUnsignedShortLE(); + + if (type == MSG_HELLO) { + sendResponse(channel, remoteAddress, index, type, "hello"); + } else { + sendResponse(channel, remoteAddress, index, type, "OK"); + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + switch (type) { + case MSG_SEND_EVENTS: + decodeEvents(position, buf); + getLastLocation(position, null); // TODO read timestamp + return position; + case MSG_SEND_TECH_INFO: + decodeTechInfo(position, buf); + getLastLocation(position, null); + return position; + case MSG_SEND_GPS_DATA: + decodeGpsData(position, buf.readSlice(length)); + return position; + case MSG_SEND_CONCATENATED_PACKET: + int count = buf.readUnsignedShortLE(); + for (int i = 0; i < count; i++) { + int partType = buf.readUnsignedShortLE(); + int partLength = buf.readUnsignedShortLE(); + switch (partType) { + case MSG_SEND_EVENTS: + decodeEvents(position, buf); + break; + case MSG_SEND_TECH_INFO: + decodeTechInfo(position, buf); + break; + case MSG_SEND_GPS_DATA: + decodeGpsData(position, buf.readSlice(partLength)); + break; + default: + buf.skipBytes(partLength); + break; + } + } + if (position.getFixTime() == null) { + getLastLocation(position, null); + } + return position; + default: + return null; + } + } + +} diff --git a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java index 53c4a5d02..c9d6f16ef 100644 --- a/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/SuntechProtocolDecoder.java @@ -295,6 +295,60 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { return position; } + private int decodeSerialData(Position position, String[] values, int index) { + + int remaining = Integer.parseInt(values[index++]); + double totalFuel = 0; + while (remaining > 0) { + String attribute = values[index++]; + if (attribute.startsWith("CabAVL")) { + String[] data = attribute.split(","); + double fuel1 = Double.parseDouble(data[2]); + if (fuel1 > 0) { + totalFuel += fuel1; + position.set("fuel1", fuel1); + } + double fuel2 = Double.parseDouble(data[3]); + if (fuel2 > 0) { + totalFuel += fuel2; + position.set("fuel2", fuel2); + } + } else if (attribute.startsWith("GTSL")) { + position.set(Position.KEY_DRIVER_UNIQUE_ID, attribute.split("\\|")[4]); + } else if (attribute.contains("=")) { + String[] pair = attribute.split("="); + if (pair.length >= 2) { + String value = pair[1].trim(); + if (value.contains(".")) { + value = value.substring(0, value.indexOf('.')); + } + switch (pair[0].charAt(0)) { + case 't': + position.set(Position.PREFIX_TEMP + pair[0].charAt(2), Integer.parseInt(value, 16)); + break; + case 'N': + int fuel = Integer.parseInt(value, 16); + totalFuel += fuel; + position.set("fuel" + pair[0].charAt(2), fuel); + break; + case 'Q': + position.set("drivingQuality", Integer.parseInt(value, 16)); + break; + default: + break; + } + } + } else { + position.set("serial", attribute.trim()); + } + remaining -= attribute.length() + 1; + } + if (totalFuel > 0) { + position.set(Position.KEY_FUEL_LEVEL, totalFuel); + } + return index + 1; // checksum + } + private Position decode2356( Channel channel, SocketAddress remoteAddress, String protocol, String[] values) throws ParseException { int index = 0; @@ -371,56 +425,7 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_ALARM, decodeAlert(Integer.parseInt(values[index++]))); break; case "UEX": - int remaining = Integer.parseInt(values[index++]); - double totalFuel = 0; - while (remaining > 0) { - String attribute = values[index++]; - if (attribute.startsWith("CabAVL")) { - String[] data = attribute.split(","); - double fuel1 = Double.parseDouble(data[2]); - if (fuel1 > 0) { - totalFuel += fuel1; - position.set("fuel1", fuel1); - } - double fuel2 = Double.parseDouble(data[3]); - if (fuel2 > 0) { - totalFuel += fuel2; - position.set("fuel2", fuel2); - } - } else if (attribute.startsWith("GTSL")) { - position.set(Position.KEY_DRIVER_UNIQUE_ID, attribute.split("\\|")[4]); - } else if (attribute.contains("=")) { - String[] pair = attribute.split("="); - if (pair.length >= 2) { - String value = pair[1].trim(); - if (value.contains(".")) { - value = value.substring(0, value.indexOf('.')); - } - switch (pair[0].charAt(0)) { - case 't': - position.set(Position.PREFIX_TEMP + pair[0].charAt(2), Integer.parseInt(value, 16)); - break; - case 'N': - int fuel = Integer.parseInt(value, 16); - totalFuel += fuel; - position.set("fuel" + pair[0].charAt(2), fuel); - break; - case 'Q': - position.set("drivingQuality", Integer.parseInt(value, 16)); - break; - default: - break; - } - } - } else { - position.set("serial", attribute.trim()); - } - remaining -= attribute.length() + 1; - } - if (totalFuel > 0) { - position.set(Position.KEY_FUEL_LEVEL, totalFuel); - } - index += 1; // checksum + index = decodeSerialData(position, values, index); break; default: break; @@ -482,7 +487,8 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { String type = values[index++]; - if (!type.equals("STT") && !type.equals("ALT") && !type.equals("BLE") && !type.equals("RES")) { + if (!type.equals("STT") && !type.equals("ALT") && !type.equals("BLE") && !type.equals("RES") + && !type.equals("UEX")) { return null; } @@ -601,34 +607,40 @@ public class SuntechProtocolDecoder extends BaseProtocolDecoder { position.set(Position.KEY_OUTPUT, Integer.parseInt(values[index++])); } - if (type.equals("ALT")) { - if (BitUtil.check(mask, 19)) { - int alertId = Integer.parseInt(values[index++]); - position.set(Position.KEY_ALARM, decodeAlert(alertId)); - } - if (BitUtil.check(mask, 20)) { - position.set("alertModifier", values[index++]); - } - if (BitUtil.check(mask, 21)) { - position.set("alertData", values[index++]); - } - } else { - if (BitUtil.check(mask, 19)) { - position.set("mode", Integer.parseInt(values[index++])); - } - if (BitUtil.check(mask, 20)) { - position.set("reason", Integer.parseInt(values[index++])); - } - if (BitUtil.check(mask, 21)) { - position.set(Position.KEY_INDEX, Integer.parseInt(values[index++])); - } + switch (type) { + case "ALT": + if (BitUtil.check(mask, 19)) { + int alertId = Integer.parseInt(values[index++]); + position.set(Position.KEY_ALARM, decodeAlert(alertId)); + } + if (BitUtil.check(mask, 20)) { + position.set("alertModifier", values[index++]); + } + if (BitUtil.check(mask, 21)) { + position.set("alertData", values[index++]); + } + break; + case "UEX": + index = decodeSerialData(position, values, index); + break; + default: + if (BitUtil.check(mask, 19)) { + position.set("mode", Integer.parseInt(values[index++])); + } + if (BitUtil.check(mask, 20)) { + position.set("reason", Integer.parseInt(values[index++])); + } + if (BitUtil.check(mask, 21)) { + position.set(Position.KEY_INDEX, Integer.parseInt(values[index++])); + } + break; } if (BitUtil.check(mask, 22)) { index += 1; // reserved } - if (BitUtil.check(mask, 23)) { + if (BitUtil.check(mask, 23) && !type.equals("UEX")) { int assignMask = Integer.parseInt(values[index++], 16); for (int i = 0; i <= 30; i++) { if (BitUtil.check(assignMask, i)) { diff --git a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java index 6197c6c13..de42031d7 100644 --- a/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TeltonikaProtocolDecoder.java @@ -199,7 +199,8 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { var fmbXXX = Set.of( "FMB001", "FMB010", "FMB002", "FMB020", "FMB003", "FMB110", "FMB120", "FMB122", "FMB125", "FMB130", "FMB140", "FMU125", "FMB900", "FMB920", "FMB962", "FMB964", "FM3001", "FMB202", "FMB204", "FMB206", - "FMT100", "MTB100", "FMP100", "MSP500"); + "FMT100", "MTB100", "FMP100", "MSP500", "FMC125", "FMM125", "FMU130", "FMC130", "FMM130", "FMB150", + "FMC150", "FMM150"); register(1, null, (p, b) -> p.set(Position.PREFIX_IN + 1, b.readUnsignedByte() > 0)); register(2, null, (p, b) -> p.set(Position.PREFIX_IN + 2, b.readUnsignedByte() > 0)); @@ -231,7 +232,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { register(75, fmbXXX, (p, b) -> p.set(Position.PREFIX_TEMP + 4, b.readInt() * 0.1)); register(78, null, (p, b) -> { long driverUniqueId = b.readLongLE(); - if (driverUniqueId > 0) { + if (driverUniqueId != 0) { p.set(Position.KEY_DRIVER_UNIQUE_ID, String.format("%016X", driverUniqueId)); } }); @@ -243,9 +244,9 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { register(85, fmbXXX, (p, b) -> p.set(Position.KEY_RPM, b.readUnsignedShort())); register(87, fmbXXX, (p, b) -> p.set(Position.KEY_OBD_ODOMETER, b.readUnsignedInt())); register(89, fmbXXX, (p, b) -> p.set("fuelLevelPercentage", b.readUnsignedByte())); - register(90, null, (p, b) -> p.set(Position.KEY_DOOR, b.readUnsignedShort())); register(110, fmbXXX, (p, b) -> p.set(Position.KEY_FUEL_CONSUMPTION, b.readUnsignedShort() * 0.1)); register(113, fmbXXX, (p, b) -> p.set(Position.KEY_BATTERY_LEVEL, b.readUnsignedByte())); + register(115, fmbXXX, (p, b) -> p.set("engineTemp", b.readShort() * 0.1)); register(179, null, (p, b) -> p.set(Position.PREFIX_OUT + 1, b.readUnsignedByte() > 0)); register(180, null, (p, b) -> p.set(Position.PREFIX_OUT + 2, b.readUnsignedByte() > 0)); register(181, null, (p, b) -> p.set(Position.KEY_PDOP, b.readUnsignedShort() * 0.1)); @@ -292,6 +293,7 @@ public class TeltonikaProtocolDecoder extends BaseProtocolDecoder { } }); register(636, fmbXXX, (p, b) -> p.set("cid4g", b.readUnsignedInt())); + register(662, fmbXXX, (p, b) -> p.set(Position.KEY_DOOR, b.readUnsignedByte() > 0)); } private void decodeGh3000Parameter(Position position, int id, ByteBuf buf, int length) { diff --git a/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java b/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java index 02744f8ab..1187250f8 100644 --- a/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java +++ b/src/main/java/org/traccar/protocol/TrvProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,6 +64,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder { .number("(d+),") // mnc .number("(d+),") // lac .number("(d+)") // cell + .number(",(dd)").optional() // alarm .groupBegin() .text(",") .expression("(") @@ -77,7 +78,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder { .any() .compile(); - private static final Pattern PATTERN_HEARTBEAT = new PatternBuilder() + private static final Pattern PATTERN_CP01 = new PatternBuilder() .expression("[A-Z]{2,3}") .text("CP01,") .number("(ddd)") // gsm @@ -99,7 +100,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder { .any() .compile(); - private static final Pattern PATTERN_LBS = new PatternBuilder() + private static final Pattern PATTERN_AP02 = new PatternBuilder() .expression("[A-Z]{2,3}") .text("AP02,") .expression("[^,]+,") // language @@ -118,6 +119,19 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder { .expression("(.*)") // wifi .compile(); + private static final Pattern PATTERN_AP03 = new PatternBuilder() + .expression("[A-Z]{2,3}") + .text("AP03,") + .number("(ddd)") // rssi + .number("(ddd)") // satellites + .number("(ddd)") // battery level + .number("d") // space + .number("xx") // fortification state + .number("dd,") // working mode + .number("(d+),") // steps + .number("d+") // rolls frequency + .compile(); + private Boolean decodeOptionalValue(Parser parser, int activeValue) { int value = parser.nextInt(); if (value != 0) { @@ -183,7 +197,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder { if (type.equals("CP01")) { - Parser parser = new Parser(PATTERN_HEARTBEAT, sentence); + Parser parser = new Parser(PATTERN_CP01, sentence); if (!parser.matches()) { return null; } @@ -234,6 +248,20 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder { parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextInt())); if (parser.hasNext()) { + switch (parser.nextInt()) { + case 1: + position.set(Position.KEY_ALARM, Position.ALARM_SOS); + break; + case 5: + case 6: + position.set(Position.KEY_ALARM, Position.ALARM_FALL_DOWN); + break; + default: + break; + } + } + + if (parser.hasNext()) { decodeWifi(network, parser.next()); } @@ -243,7 +271,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder { } else if (type.equals("AP02")) { - Parser parser = new Parser(PATTERN_LBS, sentence); + Parser parser = new Parser(PATTERN_AP02, sentence); if (!parser.matches()) { return null; } @@ -275,6 +303,61 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder { return position; + } else if (type.equals("AP03")) { + + Parser parser = new Parser(PATTERN_AP03, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + position.set(Position.KEY_RSSI, parser.nextInt()); + position.set(Position.KEY_SATELLITES, parser.nextInt()); + position.set(Position.KEY_BATTERY_LEVEL, parser.nextInt()); + position.set(Position.KEY_STEPS, parser.nextInt()); + + return position; + + } else if (type.equals("AP49") || type.equals("APHT") || type.equals("APHP") || type.equals("AP50")) { + + Position position = new Position(getProtocolName()); + position.setDeviceId(deviceSession.getDeviceId()); + + getLastLocation(position, null); + + String[] values = sentence.split(","); + + switch (type) { + case "AP49": + position.set(Position.KEY_HEART_RATE, Integer.parseInt(values[1])); + break; + case "APHT": + position.set(Position.KEY_HEART_RATE, Integer.parseInt(values[1])); + position.set("pressureSystolic", Integer.parseInt(values[2])); + position.set("pressureDiastolic", Integer.parseInt(values[3])); + break; + case "APHP": + position.set(Position.KEY_HEART_RATE, Integer.parseInt(values[1])); + position.set("pressureSystolic", Integer.parseInt(values[2])); + position.set("pressureDiastolic", Integer.parseInt(values[3])); + position.set("spo2", Integer.parseInt(values[4])); + position.set("bloodSugar", Double.parseDouble(values[5])); + position.set("temperature", Double.parseDouble(values[6])); + break; + case "AP50": + position.set("temperature", Double.parseDouble(values[1])); + position.set(Position.KEY_BATTERY_LEVEL, Integer.parseInt(values[2])); + break; + default: + break; + } + + return position; + } return null; diff --git a/src/main/java/org/traccar/reports/SummaryReportProvider.java b/src/main/java/org/traccar/reports/SummaryReportProvider.java index ffde0b067..5bd7e51b3 100644 --- a/src/main/java/org/traccar/reports/SummaryReportProvider.java +++ b/src/main/java/org/traccar/reports/SummaryReportProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -105,16 +105,11 @@ public class SummaryReportProvider { result.setDistance(PositionUtil.calculateDistance(first, last, !ignoreOdometer)); result.setSpentFuel(reportUtils.calculateFuel(first, last)); - long durationMilliseconds; if (first.hasAttribute(Position.KEY_HOURS) && last.hasAttribute(Position.KEY_HOURS)) { - durationMilliseconds = last.getLong(Position.KEY_HOURS) - first.getLong(Position.KEY_HOURS); - result.setEngineHours(durationMilliseconds); - } else { - durationMilliseconds = last.getFixTime().getTime() - first.getFixTime().getTime(); - } - - if (durationMilliseconds > 0) { - result.setAverageSpeed(UnitsConverter.knotsFromMps(result.getDistance() * 1000 / durationMilliseconds)); + result.setStartHours(first.getLong(Position.KEY_HOURS)); + result.setEndHours(last.getLong(Position.KEY_HOURS)); + result.setAverageSpeed(UnitsConverter.knotsFromMps( + result.getDistance() * 1000 / result.getEngineHours())); } if (!ignoreOdometer @@ -142,15 +137,13 @@ public class SummaryReportProvider { if (daily) { while (from.truncatedTo(ChronoUnit.DAYS).isBefore(to.truncatedTo(ChronoUnit.DAYS))) { ZonedDateTime fromDay = from.truncatedTo(ChronoUnit.DAYS); - ZonedDateTime nextDay = fromDay.plus(1, ChronoUnit.DAYS); + ZonedDateTime nextDay = fromDay.plusDays(1); results.addAll(calculateDeviceResult( device, Date.from(from.toInstant()), Date.from(nextDay.toInstant()), fast)); from = nextDay; } - results.addAll(calculateDeviceResult(device, Date.from(from.toInstant()), Date.from(to.toInstant()), fast)); - } else { - results.addAll(calculateDeviceResult(device, Date.from(from.toInstant()), Date.from(to.toInstant()), fast)); } + results.addAll(calculateDeviceResult(device, Date.from(from.toInstant()), Date.from(to.toInstant()), fast)); return results; } diff --git a/src/main/java/org/traccar/reports/model/SummaryReportItem.java b/src/main/java/org/traccar/reports/model/SummaryReportItem.java index 44a15cf1d..b88cabe49 100644 --- a/src/main/java/org/traccar/reports/model/SummaryReportItem.java +++ b/src/main/java/org/traccar/reports/model/SummaryReportItem.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2024 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,13 +18,28 @@ package org.traccar.reports.model; public class SummaryReportItem extends BaseReportItem { - private long engineHours; // milliseconds - public long getEngineHours() { - return engineHours; + return endHours - startHours; + } + + private long startHours; // milliseconds + + public long getStartHours() { + return startHours; + } + + public void setStartHours(long startHours) { + this.startHours = startHours; } - public void setEngineHours(long engineHours) { - this.engineHours = engineHours; + private long endHours; // milliseconds + + public long getEndHours() { + return endHours; } + + public void setEndHours(long endHours) { + this.endHours = endHours; + } + } diff --git a/src/main/java/org/traccar/schedule/TaskExpirations.java b/src/main/java/org/traccar/schedule/TaskExpirations.java index 94f855c5f..e16dcd86c 100644 --- a/src/main/java/org/traccar/schedule/TaskExpirations.java +++ b/src/main/java/org/traccar/schedule/TaskExpirations.java @@ -111,7 +111,7 @@ public class TaskExpirations implements ScheduleTask { } if (config.getBoolean(Keys.NOTIFICATION_EXPIRATION_DEVICE)) { - long reminder = config.getLong(Keys.NOTIFICATION_EXPIRATION_USER_REMINDER); + long reminder = config.getLong(Keys.NOTIFICATION_EXPIRATION_DEVICE_REMINDER); var devices = storage.getObjects(Device.class, new Request(new Columns.All())); for (Device device : devices) { if (checkTimeTrigger(device, currentTime, 0)) { diff --git a/src/main/java/org/traccar/schedule/TaskReports.java b/src/main/java/org/traccar/schedule/TaskReports.java index e0fa6f8d6..070fa9d2b 100644 --- a/src/main/java/org/traccar/schedule/TaskReports.java +++ b/src/main/java/org/traccar/schedule/TaskReports.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2023 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import com.google.inject.servlet.ServletScopes; import net.fortuna.ical4j.model.Period; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.traccar.helper.LogAction; import org.traccar.model.BaseModel; import org.traccar.model.Calendar; import org.traccar.model.Device; @@ -42,7 +43,9 @@ import org.traccar.storage.query.Request; import jakarta.inject.Inject; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -80,8 +83,9 @@ public class TaskReports implements ScheduleTask { var lastEvents = calendar.findPeriods(lastCheck); var currentEvents = calendar.findPeriods(currentCheck); - if (!lastEvents.isEmpty() && currentEvents.isEmpty()) { - Period period = lastEvents.iterator().next(); + Set<Period> finishedEvents = new HashSet<>(lastEvents); + finishedEvents.removeAll(currentEvents); + for (Period period : finishedEvents) { RequestScoper scope = ServletScopes.scopeRequest(Collections.emptyMap()); try (RequestScoper.CloseableScope ignored = scope.open()) { executeReport(report, period.getStart(), period.getEnd()); @@ -110,6 +114,7 @@ public class TaskReports implements ScheduleTask { ReportMailer reportMailer = injector.getInstance(ReportMailer.class); for (User user : users) { + LogAction.report(user.getId(), true, report.getType(), from, to, deviceIds, groupIds); switch (report.getType()) { case "events": var eventsReportProvider = injector.getInstance(EventsReportProvider.class); diff --git a/src/main/java/org/traccar/storage/DatabaseModule.java b/src/main/java/org/traccar/storage/DatabaseModule.java index 9d9e5bd5e..cd66d7298 100644 --- a/src/main/java/org/traccar/storage/DatabaseModule.java +++ b/src/main/java/org/traccar/storage/DatabaseModule.java @@ -24,6 +24,7 @@ import liquibase.Liquibase; import liquibase.database.Database; import liquibase.database.DatabaseFactory; import liquibase.exception.LiquibaseException; +import liquibase.exception.LockException; import liquibase.resource.DirectoryResourceAccessor; import liquibase.resource.ResourceAccessor; import org.traccar.config.Config; @@ -78,22 +79,27 @@ public class DatabaseModule extends AbstractModule { DataSource dataSource = new HikariDataSource(hikariConfig); - if (config.hasKey(Keys.DATABASE_CHANGELOG)) { + String changelog = config.getString(Keys.DATABASE_CHANGELOG); + if (changelog != null && !changelog.isEmpty()) { ResourceAccessor resourceAccessor = new DirectoryResourceAccessor(new File(".")); - Database database = DatabaseFactory.getInstance().openDatabase( - config.getString(Keys.DATABASE_URL), - config.getString(Keys.DATABASE_USER), - config.getString(Keys.DATABASE_PASSWORD), - config.getString(Keys.DATABASE_DRIVER), - null, null, null, resourceAccessor); + System.setProperty("liquibase.changelogLockWaitTimeInMinutes", "1"); - String changelog = config.getString(Keys.DATABASE_CHANGELOG); - - try (Liquibase liquibase = new Liquibase(changelog, resourceAccessor, database)) { - liquibase.clearCheckSums(); - liquibase.update(new Contexts()); + try { + Database database = DatabaseFactory.getInstance().openDatabase( + config.getString(Keys.DATABASE_URL), + config.getString(Keys.DATABASE_USER), + config.getString(Keys.DATABASE_PASSWORD), + config.getString(Keys.DATABASE_DRIVER), + null, null, null, resourceAccessor); + + try (Liquibase liquibase = new Liquibase(changelog, resourceAccessor, database)) { + liquibase.clearCheckSums(); + liquibase.update(new Contexts()); + } + } catch (LockException e) { + throw new DatabaseLockException(); } } @@ -101,3 +107,12 @@ public class DatabaseModule extends AbstractModule { } } + +class DatabaseLockException extends RuntimeException { + DatabaseLockException() { + super("Database is in a locked state. " + + "It could be due to early service termination on a previous launch. " + + "To unlock you can run this query: 'UPDATE DATABASECHANGELOGLOCK SET locked = 0'. " + + "Make sure the schema is up to date before unlocking the database."); + } +} diff --git a/src/main/java/org/traccar/web/ModernDefaultServlet.java b/src/main/java/org/traccar/web/DefaultOverrideServlet.java index a7c8cdb29..14b441f86 100644 --- a/src/main/java/org/traccar/web/ModernDefaultServlet.java +++ b/src/main/java/org/traccar/web/DefaultOverrideServlet.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 Anton Tananaev (anton@traccar.org) + * Copyright 2023 - 2024 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,12 +24,12 @@ import jakarta.inject.Inject; import java.io.File; import java.io.IOException; -public class ModernDefaultServlet extends DefaultServlet { +public class DefaultOverrideServlet extends DefaultServlet { private Resource overrideResource; @Inject - public ModernDefaultServlet(Config config) { + public DefaultOverrideServlet(Config config) { String override = config.getString(Keys.WEB_OVERRIDE); if (override != null) { overrideResource = Resource.newResource(new File(override)); diff --git a/src/main/java/org/traccar/web/WebServer.java b/src/main/java/org/traccar/web/WebServer.java index 4759942b1..68fcf52de 100644 --- a/src/main/java/org/traccar/web/WebServer.java +++ b/src/main/java/org/traccar/web/WebServer.java @@ -139,7 +139,7 @@ public class WebServer implements LifecycleObject { } private void initWebApp(ServletContextHandler servletHandler) { - ServletHolder servletHolder = new ServletHolder(new ModernDefaultServlet(config)); + ServletHolder servletHolder = new ServletHolder(new DefaultOverrideServlet(config)); servletHolder.setInitParameter("resourceBase", new File(config.getString(Keys.WEB_PATH)).getAbsolutePath()); servletHolder.setInitParameter("dirAllowed", "false"); if (config.getBoolean(Keys.WEB_DEBUG)) { diff --git a/src/test/java/org/traccar/calendar/CalendarTest.java b/src/test/java/org/traccar/calendar/CalendarTest.java index 4106f89a9..d2c2b251c 100644 --- a/src/test/java/org/traccar/calendar/CalendarTest.java +++ b/src/test/java/org/traccar/calendar/CalendarTest.java @@ -5,19 +5,19 @@ import org.junit.jupiter.api.Test; import org.traccar.model.Calendar; import java.io.IOException; -import java.sql.SQLException; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.Date; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class CalendarTest { @Test - public void testCalendar() throws IOException, ParserException, ParseException, SQLException { + public void testCalendar() throws IOException, ParserException, ParseException { String calendarString = "BEGIN:VCALENDAR\n" + "PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\n" + "VERSION:2.0\n" + @@ -54,4 +54,36 @@ public class CalendarTest { var periods = calendar.findPeriods(format.parse("2016-12-13 06:59:59+05")); assertFalse(periods.isEmpty()); } + + @Test + public void testCalendarOverlap() throws IOException, ParserException, ParseException { + String calendarString = "BEGIN:VCALENDAR\n" + + "VERSION:2.0\n" + + "PRODID:-//Traccar//NONSGML Traccar//EN\n" + + "BEGIN:VEVENT\n" + + "UID:00000000-0000-0000-0000-000000000000\n" + + "DTSTART;TZID=America/Los_Angeles:20240420T060000\n" + + "DTEND;TZID=America/Los_Angeles:20240421T060000\n" + + "RRULE:FREQ=DAILY\n" + + "SUMMARY:Event\n" + + "END:VEVENT\n" + + "END:VCALENDAR"; + Calendar calendar = new Calendar(); + calendar.setData(calendarString.getBytes()); + DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssX"); + + var periods0 = calendar.findPeriods(format.parse("2014-05-13 07:00:00-07")); + var periods1 = calendar.findPeriods(format.parse("2024-05-13 05:00:00-07")); + var periods2 = calendar.findPeriods(format.parse("2024-05-13 07:00:00-07")); + var periods3 = calendar.findPeriods(format.parse("2024-05-13 08:00:00-07")); + + assertEquals(periods0.size(), 0); + assertEquals(periods1.size(), 1); + assertEquals(periods2.size(), 1); + assertEquals(periods3.size(), 1); + + assertNotEquals(periods0, periods1); + assertNotEquals(periods1, periods2); + assertEquals(periods2, periods3); + } } diff --git a/src/test/java/org/traccar/protocol/EasyTrackProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/EasyTrackProtocolDecoderTest.java index d40463c1e..fee0252b7 100644 --- a/src/test/java/org/traccar/protocol/EasyTrackProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/EasyTrackProtocolDecoderTest.java @@ -11,6 +11,9 @@ public class EasyTrackProtocolDecoderTest extends ProtocolTest { var decoder = inject(new EasyTrackProtocolDecoder(null)); verifyAttributes(decoder, text( + "*ET,358162092884226,OB,BD$V13.6;R01510;S023;P034.9;O035.2;C085;L000.0;XM035.170;M4.25;F001.197;T0000730;A09;B00;D00;GX0;GY0;GZ0;")); + + verifyAttributes(decoder, text( "*ET,358999999999916,OB,BD$V14.2;R08258;S166;P058.4;O079.2;C025;L081.5;XM091.393;M722379;F352.956;T0037184;A01;B00;D00;GX3;GY-6;GZ-268;@4#")); verifyNotNull(decoder, text( diff --git a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java index 4cccf8fa2..925a0da1c 100644 --- a/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Gl200TextProtocolDecoderTest.java @@ -12,6 +12,10 @@ public class Gl200TextProtocolDecoderTest extends ProtocolTest { var decoder = inject(new Gl200TextProtocolDecoder(null)); verifyAttribute(decoder, buffer( + "+RESP:GTERI,8020050704,867488060246195,,00000004,28823,10,1,1,0.0,33,10.1,10.606120,43.656780,20240408084402,0222,0010,7D53,00DD120D,02,0,0.0,,,,,100,210100,0,1,FFFFF,YS2R4X20009288827,2,H1910197,58234.30,500,1,90,H1.5,P84.00,,0,4616.20,2.28,2.16,5.64,5358,1038,0010,00,00,20240408084403,1809$"), + Position.KEY_BATTERY_LEVEL, 100); + + verifyAttribute(decoder, buffer( "+RESP:GTVGN,C2010D,869653060009939,GV600M,453966,0,0.0,0,163.9,10.239379,45.931389,20231210233723,0222,0010,2F31,006D7621,,,0.0,20240107182623,143F$"), Position.KEY_IGNITION, true); diff --git a/src/test/java/org/traccar/protocol/GlobalstarProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/GlobalstarProtocolDecoderTest.java index 995fffad0..e78c6c7e7 100644 --- a/src/test/java/org/traccar/protocol/GlobalstarProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/GlobalstarProtocolDecoderTest.java @@ -11,7 +11,7 @@ public class GlobalstarProtocolDecoderTest extends ProtocolTest { var decoder = inject(new GlobalstarProtocolDecoder(null)); - decoder.setAlternative(true); + decoder.setModelOverride("AtlasTrax"); verifyNull(decoder, request(HttpMethod.POST, "/", buffer( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", @@ -24,7 +24,7 @@ public class GlobalstarProtocolDecoderTest extends ProtocolTest { "</stuMessage>\n", "</stuMessages>"))); - decoder.setAlternative(false); + decoder.setModelOverride(null); verifyPositions(decoder, request(HttpMethod.POST, "/", buffer( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>", diff --git a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java index 04d7fafb3..05a33cc72 100644 --- a/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/Gt06ProtocolDecoderTest.java @@ -494,6 +494,12 @@ public class Gt06ProtocolDecoderTest extends ProtocolTest { "78785195140a020c2914055D4A800209D9C014009300004556454e545f3335333337363131303032333139365f30303030303030305f323032305f31305f30325f31345f34315f32305f30352e6d70340004e3a60d0a"), Position.KEY_ALARM, Position.ALARM_ACCIDENT); + decoder.setModelOverride("LW4G-4B"); + + verifyAttribute(decoder, binary( + "78782516180516150812c804b50ee80880e40805dcf909012e000000986633460604190106c393490d0a"), + Position.KEY_ALARM, Position.ALARM_ACCELERATION); + } } diff --git a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java index 7fd7cc599..2ba37ab09 100644 --- a/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/HuabaoProtocolDecoderTest.java @@ -12,6 +12,14 @@ public class HuabaoProtocolDecoderTest extends ProtocolTest { var decoder = inject(new HuabaoProtocolDecoder(null)); verifyAttribute(decoder, binary( + "7e020000520198080908740af300000000000c000f016602a302c662f802fc000000cc24051618132401040000020f30011e310109f30100610204b056020acd5d0b0102d40379b8011423033c51108017ffffffffffffffffffffffffffffc87e"), + Position.PREFIX_TEMP + 1, -32745); + + verifyAttribute(decoder, binary( + "7e020000460100503769640002000001000000001a01b9eaf804d8ee86001800000000240507035152010400000000300100310107eb1c00060089fffffffe000400ce0000000c00b28942310221007544309f5f7e"), + Position.KEY_ICCID, "8942310221007544309"); + + verifyAttribute(decoder, binary( "7E0200005300959000194400080000000000000003015DA64806CCB4A8001100000000230816014137010400005B3F30011F310112EB29000C00B28986049910219020787400060089FFFFFFFF000600C5FFFFFFEF0004002D0F4E000300A84CE07E"), Position.KEY_BATTERY_LEVEL, 76); diff --git a/src/test/java/org/traccar/protocol/SnapperFrameDecoderTest.java b/src/test/java/org/traccar/protocol/SnapperFrameDecoderTest.java new file mode 100644 index 000000000..260a18032 --- /dev/null +++ b/src/test/java/org/traccar/protocol/SnapperFrameDecoderTest.java @@ -0,0 +1,23 @@ +package org.traccar.protocol; + +import org.junit.jupiter.api.Test; +import org.traccar.ProtocolTest; + +public class SnapperFrameDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + var decoder = inject(new SnapperFrameDecoder()); + + verifyFrame( + binary("4b0341a6b0c608000040000005000000000000007d5e14010068656c6c6f"), + decoder.decode(null, null, binary("4b0341a6b0c608000040000005000000000000007d5e14010068656c6c6f"))); + + verifyFrame( + binary("5012"), + decoder.decode(null, null, binary("5012"))); + + } + +} diff --git a/src/test/java/org/traccar/protocol/SnapperProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/SnapperProtocolDecoderTest.java new file mode 100644 index 000000000..43df24316 --- /dev/null +++ b/src/test/java/org/traccar/protocol/SnapperProtocolDecoderTest.java @@ -0,0 +1,30 @@ +package org.traccar.protocol; + +import org.junit.jupiter.api.Test; +import org.traccar.ProtocolTest; + +public class SnapperProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + var decoder = inject(new SnapperProtocolDecoder(null)); + + verifyNull(decoder, binary( + "4b0341a6b0c608000040000005000000000000007d5e14010068656c6c6f")); + + verifyAttributes(decoder, binary( + "4b044daff87aff5b8aad0000d4000000000000000b001337000500210008000d0000ffffffff7f2300190000000000000001000000000224ff000003404000000400000034008c007b2273223a22303034313438222c226332223a2230303030303030303030303030303030222c226132223a2230303030303030303030303830304530222c226f223a2230303030222c2274223a2230303030222c227a223a223030222c2277223a223030222c2272223a222d222c226d223a223030303030303030222c2262223a223030303030303030227d32000a007b2266223a223234227d330007007b2262223a5d7d")); + + verifyPosition(decoder, binary( + "4b044daff87aff5b8aad00007b010000000013ea0c006837000500210008003e000058c48fa94823001900000080000200018080000002deff0f0003404000000400000034008c007b2273223a22303034303438222c226332223a2230303030303030303030303030303030222c226132223a2230303030303030303030303030303030222c226f223a2230303030222c2274223a2230303030222c227a223a223030222c2277223a223030222c2272223a222d222c226d223a223030303030303030222c2262223a223030303030303030227d320079007b2266223a224445222c2274223a22303932383336222c2264223a22313530343234222c226c61223a22353334312e34333732222c226c6f223a2230303935342e30373036222c2261223a22382e34222c2273223a22302e3030222c2263223a2232362e3036222c227376223a223135222c2270223a22227d33003f007b2263223a22323632222c226e223a223033222c2262223a5b7b226c223a2232423334222c2263223a223030303041313231222c2273223a223132227d5d7d")); + + verifyPosition(decoder, binary( + "4b044daff87aff5b8aad0000430100000000bb870c005b37000500210008003e0100ffffffff7f2300190000007f000000018080000002deff080003404000000400000034008c007b2273223a22303034303438222c226332223a2230303030303030303030303030303030222c226132223a2230303030303030303030303030303030222c226f223a2230303030222c2274223a2230303030222c227a223a223030222c2277223a223030222c2272223a222d222c226d223a223030303030303030222c2262223a223030303030303030227d320079007b2266223a224445222c2274223a22303832303335222c2264223a22313730343234222c226c61223a22353334312e34323635222c226c6f223a2230303935342e30373935222c2261223a22362e32222c2273223a22322e3237222c2263223a2233302e3135222c227376223a223038222c2270223a22227d330007007b2262223a5d7d")); + + verifyNull(decoder, binary( + "5003")); + + } + +} diff --git a/src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java index d656bba13..67fa43dec 100644 --- a/src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/SuntechProtocolDecoderTest.java @@ -12,6 +12,10 @@ public class SuntechProtocolDecoderTest extends ProtocolTest { var decoder = inject(new SuntechProtocolDecoder(null)); + verifyAttribute(decoder, buffer( + "UEX;1610020241;03FFFFFF;161;3.0.9;0;20240506;15:52:55;00006697;724;11;4EDA;33;-5.129240;-42.797868;0.00;0.00;11;1;00000001;00000000;24;GTSL|6|1|0|22574684|1|\r\n;A7;;164;0;11.82"), + Position.KEY_DRIVER_UNIQUE_ID, "22574684"); + verifyAttributes(decoder, buffer( "ST410STT;109815653;445;03;16531;724;10;-77;6011;7;16273;724;10;7511;0;0;13903;724;10;6011;0;0;7671;724;10;7111;0;0;16533;724;10;6011;0;0;0;0;0;0;0;0;0;0;0;0;0;0;3.86;0;0098;1;003;;;;;;;;;")); diff --git a/src/test/java/org/traccar/protocol/TrvProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/TrvProtocolDecoderTest.java index 370775735..a17fc341f 100644 --- a/src/test/java/org/traccar/protocol/TrvProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/TrvProtocolDecoderTest.java @@ -2,6 +2,7 @@ package org.traccar.protocol; import org.junit.jupiter.api.Test; import org.traccar.ProtocolTest; +import org.traccar.model.Position; public class TrvProtocolDecoderTest extends ProtocolTest { @@ -13,6 +14,30 @@ public class TrvProtocolDecoderTest extends ProtocolTest { verifyNull(decoder, text( "TRVAP00352121088015548")); + verifyAttribute(decoder, text( + "IWAP10080524A2232.9806N11404.9355E000.1061830323.8706000908000502,460,0,9520,3671,01,zhcn,00,HOME|74-DE-2B-44-88-8C|97&HOME1|74-DE-2B-44-88-8C|97&HOME2|74-DE-2B-44-88-8C|97&HOME3|74-DE-2B-44-88-8C|97"), + Position.KEY_ALARM, Position.ALARM_SOS); + + verifyAttribute(decoder, text( + "IWAP49,68"), + Position.KEY_HEART_RATE, 68); + + verifyAttribute(decoder, text( + "IWAPHT,60,130,85"), + "pressureDiastolic", 85); + + verifyAttribute(decoder, text( + "IWAPHP,60,130,85,95,90,36.5,,,,,,,"), + "temperature", 36.5); + + verifyAttribute(decoder, text( + "IWAP50,36.7,90"), + Position.KEY_BATTERY_LEVEL, 90); + + verifyAttribute(decoder, text( + "IWAP03,06000908000102,5555,30"), + Position.KEY_STEPS, 5555); + verifyPosition(decoder, text( "TRVYP14080524A2232.9806N11404.9355E000.1061830323.870600090800010200011,460,0,9520,3671,Home|74-DE-2B-44-88-8C|97&Home1|74-DE-2B-44-88-8C|97&Home2|74-DE-2B-44-88-8C|97& Home3|74-DE-2B-44-88-8C|97")); @@ -40,7 +65,7 @@ public class TrvProtocolDecoderTest extends ProtocolTest { verifyPosition(decoder, text( "IWAP10080524A2232.9806N11404.9355E000.1061830323.8706000908000502,460,0,9520,3671,00,zh-cn,00,HOME|74-DE-2B-44-88-8C|97&HOME1|74-DE-2B-44-88-8C|97&HOME2|74-DE-2B-44-88-8C|97&HOME3|74-DE-2B-44-88-8C|97")); - verifyNull(decoder, text( + verifyAttributes(decoder, text( "IWAP03,06000908000102,5555,30")); verifyNull(decoder, text( diff --git a/src/test/java/org/traccar/protocol/WatchProtocolDecoderTest.java b/src/test/java/org/traccar/protocol/WatchProtocolDecoderTest.java index 5fd0ede44..5c61d5426 100644 --- a/src/test/java/org/traccar/protocol/WatchProtocolDecoderTest.java +++ b/src/test/java/org/traccar/protocol/WatchProtocolDecoderTest.java @@ -18,7 +18,7 @@ public class WatchProtocolDecoderTest extends ProtocolTest { var decoder = inject(new WatchProtocolDecoder(null)); verifyAttribute(decoder, buffer( - "[3G*9705141740*000B*oxygen,0,98]"), + "[3G*9705141740*000B*oxygen,0,98]"), "bloodOxygen", 98); verifyPosition(decoder, buffer( diff --git a/swagger.json b/swagger.json index 933652cb0..b6d1ff241 100644 --- a/swagger.json +++ b/swagger.json @@ -2,7 +2,7 @@ "openapi": "3.0.1", "info": { "title": "Traccar", - "version": "6.0", + "version": "6.2", "description": "Traccar GPS tracking server API documentation. To use the API you need to have a server instance. For testing purposes you can use one of free [demo servers](https://www.traccar.org/demo-server/). For production use you can install your own server or get a [subscription service](https://www.traccar.org/product/tracking-server/).", "contact": { "name": "Traccar Support", diff --git a/templates/export/stops.xlsx b/templates/export/stops.xlsx Binary files differindex 967d26309..4e7aaf422 100644 --- a/templates/export/stops.xlsx +++ b/templates/export/stops.xlsx diff --git a/templates/export/summary.xlsx b/templates/export/summary.xlsx Binary files differindex 47a6241b1..528b99f55 100644 --- a/templates/export/summary.xlsx +++ b/templates/export/summary.xlsx diff --git a/templates/export/trips.xlsx b/templates/export/trips.xlsx Binary files differindex 3201c66bb..3549a372e 100644 --- a/templates/export/trips.xlsx +++ b/templates/export/trips.xlsx diff --git a/tools/config-doc.py b/tools/config-doc.py index c55b2b5ef..9d9177070 100755 --- a/tools/config-doc.py +++ b/tools/config-doc.py @@ -19,40 +19,32 @@ def get_config_keys(): 'key': A dot separated name of the config key 'description': A list of str """ - desc_re = re.compile(r"(/\*\*\n|\s+\*/|\s+\*)") - key_match_re = re.compile(r"\(\n(.+)\);", re.DOTALL) - key_split_re = re.compile(r",\s+", re.DOTALL) types_match_re = re.compile(r"List\.of\(([^)]+)\)", re.DOTALL) keys = [] with open(_KEYS_FILE, "r") as f: - config = re.findall( - r"(/\*\*.*?\*/)\n\s+(public static final Config.*?;)", f.read(), re.DOTALL - ) + config = re.findall(r"/\*\*\s.*?\);", f.read(), re.DOTALL) for i in config: - try: - key_match = key_match_re.search(i[1]) - if key_match: - terms = [x.strip() for x in key_split_re.split(key_match.group(1))] - key = terms[0].replace('"', "") - key = "[protocol]" + key if key.startswith('.') else key - description = [ - x.strip().replace("\n", "") - for x in desc_re.sub("\n", i[0]).strip().split("\n\n") - ] - types_match = types_match_re.search(i[1]) - types = map(lambda x: x[8:].lower(), types_match[1].split(", ")) - keys.append( - { - "key": key, - "description": description, - "types": types, - } - ) - except IndexError: - # will continue if key_match.group(1) or terms[0] does not exist - # for some reason - pass + lines = i.splitlines() + index = -1 + default = None + if "List.of" not in lines[index]: + default = lines[index].strip()[:-2] + index -= 1 + types_match = types_match_re.search(lines[index]) + types = map(lambda x: x[8:].lower(), types_match[1].split(", ")) + index -= 1 + key = lines[index].strip()[1:-2] + key = "[protocol]" + key if key.startswith('.') else key + description = " ".join([l.strip()[2:] for l in lines if l.startswith(" * ")]) + keys.append( + { + "key": key, + "description": description, + "types": types, + "default": default, + } + ) return keys @@ -66,7 +58,7 @@ def get_html(): {x["key"]} </h5> <p class="card-text"> - {"<br /> ".join(x["description"])} + {x["description"]} </p> </div> </div>""" @@ -81,7 +73,7 @@ def get_pug(): f""" div(class='card mt-3') div(class='card-body') h5(class='card-title') {x["key"]} {" ".join(map("#[span(class='badge badge-dark') {:}]".format, x["types"]))} - p(class='card-text') {"#[br] ".join(x["description"])}""" + p(class='card-text') {x["description"]}{f"\n p(class='card-text') Default value: {x["default"]}" if x["default"] is not None else ""}""" for x in get_config_keys() ] ) diff --git a/tools/test-integration.py b/tools/test-integration.py index f31ad7a82..29c741390 100755 --- a/tools/test-integration.py +++ b/tools/test-integration.py @@ -9,6 +9,7 @@ import json import socket import time import threading +import re messages = { 'gps103' : 'imei:123456789012345,help me,1201011201,,F,120100.000,A,6000.0000,N,13000.0000,E,0.00,;', @@ -137,11 +138,11 @@ debug = '-v' in sys.argv def load_ports(): ports = {} dir = os.path.dirname(os.path.abspath(__file__)) - root = xml.etree.ElementTree.parse(dir + '/../setup/default.xml').getroot() - for entry in root.findall('entry'): - key = entry.attrib['key'] - if key.endswith('.port'): - ports[key[:-5]] = int(entry.text) + with open(dir + '/../src/main/java/org/traccar/config/PortConfigSuffix.java', 'r') as file: + content = file.read() + pattern = re.compile(r'PORTS\.put\("([^"]+)",\s*(\d+)\);') + matches = pattern.findall(content) + ports = {protocol: int(port) for protocol, port in matches} if debug: print('\nports: {ports!r}\n') return ports diff --git a/traccar-web b/traccar-web -Subproject 1a7f95baad1939589e6a23bd31b42b8e094a736 +Subproject e9cdc61de90ef44dc2edd1c82d10f2f29803715 |