├── .github ├── dependabot.yml └── workflows │ └── gradle.yml ├── .gitignore ├── .openapi-generator-ignore ├── LICENSE.md ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── fr │ └── mary │ └── olivier │ └── aw │ └── watcher │ ├── HeartBeatData.java │ ├── InfoAction.java │ ├── ReportActivity.java │ └── listener │ ├── RADocumentListener.java │ ├── RAEditorMouseListener.java │ ├── RASaveListener.java │ └── RAVisibleAreaListener.java └── resources ├── META-INF └── plugin.xml └── swagger.yaml /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 6 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle 7 | 8 | name: Java CI with Gradle 9 | 10 | on: 11 | push: 12 | branches: 13 | - "*" 14 | tags: 15 | - '*' 16 | pull_request: 17 | branches: 18 | - "*" 19 | 20 | permissions: 21 | contents: read 22 | 23 | jobs: 24 | build: 25 | 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - uses: actions/checkout@v3 30 | - name: Set up JDK 11 31 | uses: actions/setup-java@v3 32 | with: 33 | java-version: '11' 34 | distribution: 'temurin' 35 | cache: 'gradle' 36 | - name: Build with Gradle 37 | uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 38 | with: 39 | arguments: buildPlugin 40 | env: 41 | ORG_GRADLE_PROJECT_intellijPublishToken: ${{ secrets.PUBLISH_TOKEN }} 42 | - name: Publish with Gradle 43 | if: startsWith(github.ref, 'refs/tags/') 44 | uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 45 | with: 46 | arguments: publishPlugin 47 | env: 48 | ORG_GRADLE_PROJECT_intellijPublishToken: ${{ secrets.PUBLISH_TOKEN }} 49 | - name: Archive production artifacts 50 | uses: actions/upload-artifact@v3 51 | with: 52 | name: distribution 53 | path: | 54 | build/distributions/*.zip 55 | 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /src/generated/ 2 | .gradle/ 3 | .idea/ 4 | build/ 5 | swagger/ 6 | gradle.properties 7 | *.iml 8 | 9 | -------------------------------------------------------------------------------- /.openapi-generator-ignore: -------------------------------------------------------------------------------- 1 | # OpenAPI Generator Ignore 2 | # Generated by openapi-generator https://github.com/openapitools/openapi-generator 3 | 4 | # Use this file to prevent files from being overwritten by the generator. 5 | # The patterns follow closely to .gitignore or .dockerignore. 6 | 7 | # As an example, the C# client generator defines ApiClient.cs. 8 | # You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: 9 | #ApiClient.cs 10 | 11 | # You can match any string of characters against a directory, file or extension with a single asterisk (*): 12 | #foo/*/qux 13 | # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux 14 | 15 | # You can recursively match patterns against a directory, file or extension with a double asterisk (**): 16 | #foo/**/qux 17 | # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux 18 | 19 | # You can also negate patterns with an exclamation (!). 20 | # For example, you can ignore all files in a docs folder with the file extension .md: 21 | #docs/*.md 22 | # Then explicitly reverse the ignore rule for a single file: 23 | #!docs/README.md 24 | ** 25 | !**.java 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Olivier MARY 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aw-watcher-jetbrains 2 | This extension allows the open source tracking tool ActivityWatch to keep track of the projects and coding languages you use in jetbrains IDEs. 3 | 4 | ## Install 5 | You need to install "Activity Watcher" plugin in IntelliJ (using the Marketplace) 6 | 7 | ## Get Swagger from API 8 | 9 | `http://localhost:5600/api/swagger.json` 10 | 11 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.jetbrains.intellij' version '1.13.3' 4 | id 'org.hidetake.swagger.generator' version '2.19.2' 5 | } 6 | 7 | group 'fr.mary.olivier' 8 | version '2.0.9' 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | sourceSets { 15 | generated { 16 | java.srcDirs "${projectDir}/src/generated/java", "${projectDir}/swagger/src/main/java" 17 | } 18 | main { 19 | java.srcDirs += sourceSets.generated.java.srcDirs 20 | } 21 | } 22 | 23 | tasks.register('generateVersion') { 24 | doFirst { 25 | def versionFile = file("${projectDir}/src/generated/java/fr/mary/olivier/aw/watcher/Version.java") 26 | versionFile.parentFile.mkdirs() 27 | versionFile.text = """ 28 | package fr.mary.olivier.aw.watcher; 29 | 30 | public class Version { 31 | public static String getVersion() { 32 | return "$project.version"; 33 | } 34 | } 35 | """ 36 | } 37 | } 38 | 39 | swaggerSources { 40 | activityWatcher { 41 | inputFile = file('src/main/resources/swagger.yaml') 42 | 43 | code { 44 | language = 'java' 45 | outputDir = file('swagger') 46 | wipeOutputDir = true 47 | rawOptions = ["--ignore-file-override=${projectDir}/.openapi-generator-ignore"] 48 | 49 | } 50 | } 51 | } 52 | 53 | compileJava.dependsOn generateVersion, swaggerSources.activityWatcher.code 54 | 55 | clean { 56 | delete += sourceSets.generated.java.srcDirs + sourceSets.generated.resources.srcDirs + swaggerSources.activityWatcher.code.outputDir 57 | } 58 | 59 | sourceCompatibility = JavaVersion.VERSION_11 60 | targetCompatibility = JavaVersion.VERSION_11 61 | 62 | 63 | dependencies { 64 | implementation 'io.swagger:swagger-annotations:1.6.10' 65 | implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0' 66 | implementation 'com.squareup.okhttp3:okhttp:4.10.0' 67 | implementation 'com.google.code.gson:gson:2.10.1' 68 | implementation 'io.gsonfire:gson-fire:1.8.3' 69 | implementation 'javax.annotation:javax.annotation-api:1.3.2' 70 | implementation 'javax.ws.rs:javax.ws.rs-api:2.1.1' 71 | implementation 'org.openapitools:openapi-generator:6.4.0' 72 | implementation 'org.projectlombok:lombok:1.18.26' 73 | annotationProcessor 'org.projectlombok:lombok:1.18.26' 74 | testImplementation 'junit:junit:4.13.1' 75 | swaggerCodegen 'org.openapitools:openapi-generator-cli:6.4.0' 76 | } 77 | 78 | intellij { 79 | version.set('2020.3') 80 | setUpdateSinceUntilBuild(false) 81 | plugins.add('Git4Idea') 82 | // type="CL" 83 | type="GO" 84 | 85 | } 86 | patchPluginXml { 87 | setChangeNotes """ 88 | 2.0.9
89 | - Fix Some Language issues
90 | 2.0.8
91 | - Fix logs errors instead of warn
92 | 2.0.7
93 | - OkHttp3 upgrade
94 | 2.0.6
95 | - Upgrade dependencies
96 | 2.0.5
97 | - Fix initial warning
98 | 2.0.4
99 | - Fix aw-server-rust schema differences
100 | - Fix very slow aw-server issues (windows)
101 | - Use async http client
102 | 2.0.3
103 | - Fix unknown editor name in final version
104 | 2.0.2
105 | - Add more compatibility
106 | 2.0.1
107 | - Fix Call to Internal API
108 | 2.0.0
109 | - Add support for IntelliJ 2022.3
110 | - Change to HeatBeat event
111 | - Add VCS informations
112 | 1.0.8
113 | - Fix exceptions
114 | - Fix file name recorded
115 | - Fix multiple events recorded
116 | 1.0.7
117 | - Fix notes + plugin.xml comments
118 | 1.0.6
119 | - Compatibility
120 | 1.0.5
121 | - Send notification when connexion is back
122 | 1.0.4
123 | - All events resend if connexion to AW server lost
124 | - No more error log if connexion failed after ide start connexion to AW server
125 | 1.0.3
126 | - Change plugin name
127 | 1.0.2
128 | - Fix NPE.
129 | 1.0.1
130 | - Fix build.
131 | 1.0.0
132 | - Initial version""" 133 | } 134 | 135 | publishPlugin { 136 | setToken intellijPublishToken 137 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OlivierMary/aw-watcher-jetbrains/adb89b6ba62215d95e6db459665899818cc8cc7c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'aw-watcher' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/fr/mary/olivier/aw/watcher/HeartBeatData.java: -------------------------------------------------------------------------------- 1 | package fr.mary.olivier.aw.watcher; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.ToString; 7 | 8 | @EqualsAndHashCode() 9 | @ToString(callSuper = true) 10 | @Builder 11 | @Data 12 | public class HeartBeatData { 13 | private String file; 14 | private String fileFullPath; 15 | private String project; 16 | private String projectPath; 17 | private String language; 18 | private String editor; 19 | private String editorVersion; 20 | private String eventType; 21 | private String branch; 22 | private String commit; 23 | private String state; 24 | private String sourceUrl; 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/main/java/fr/mary/olivier/aw/watcher/InfoAction.java: -------------------------------------------------------------------------------- 1 | package fr.mary.olivier.aw.watcher; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.ui.Messages; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | public class InfoAction extends AnAction { 9 | 10 | @Override 11 | public void actionPerformed(@NotNull AnActionEvent e) { 12 | Messages.showMessageDialog("Activity Watcher\nVersion: " + Version.getVersion(), 13 | "Activity Watcher", Messages.getInformationIcon()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/fr/mary/olivier/aw/watcher/ReportActivity.java: -------------------------------------------------------------------------------- 1 | package fr.mary.olivier.aw.watcher; 2 | 3 | import com.intellij.AppTopics; 4 | import com.intellij.notification.Notification; 5 | import com.intellij.notification.NotificationType; 6 | import com.intellij.notification.Notifications; 7 | import com.intellij.openapi.Disposable; 8 | import com.intellij.openapi.application.ApplicationInfo; 9 | import com.intellij.openapi.application.ApplicationManager; 10 | import com.intellij.openapi.diagnostic.Logger; 11 | import com.intellij.openapi.editor.Document; 12 | import com.intellij.openapi.editor.Editor; 13 | import com.intellij.openapi.editor.EditorFactory; 14 | import com.intellij.openapi.fileTypes.LanguageFileType; 15 | import com.intellij.openapi.project.Project; 16 | import com.intellij.openapi.vfs.VirtualFile; 17 | import com.intellij.util.messages.MessageBus; 18 | import com.intellij.util.messages.MessageBusConnection; 19 | import fr.mary.olivier.aw.watcher.listener.RADocumentListener; 20 | import fr.mary.olivier.aw.watcher.listener.RAEditorMouseListener; 21 | import fr.mary.olivier.aw.watcher.listener.RASaveListener; 22 | import fr.mary.olivier.aw.watcher.listener.RAVisibleAreaListener; 23 | import git4idea.GitUtil; 24 | import git4idea.repo.GitRepositoryManager; 25 | import org.openapitools.client.ApiCallback; 26 | import org.openapitools.client.ApiException; 27 | import org.openapitools.client.api.DefaultApi; 28 | import org.openapitools.client.model.Bucket; 29 | import org.openapitools.client.model.CreateBucket; 30 | import org.openapitools.client.model.Event; 31 | 32 | import java.awt.*; 33 | import java.net.InetAddress; 34 | import java.time.OffsetDateTime; 35 | import java.util.List; 36 | import java.util.Map; 37 | import java.util.concurrent.Executors; 38 | import java.util.concurrent.ScheduledExecutorService; 39 | import java.util.concurrent.ScheduledFuture; 40 | import java.util.concurrent.TimeUnit; 41 | 42 | public class ReportActivity implements Disposable { 43 | 44 | private static final String ACTIVITY_WATCHER = "Activity Watcher"; 45 | private static final Logger LOG = Logger.getInstance(ReportActivity.class.getName()); 46 | private static final String TYPE = "app.editor.activity"; 47 | private static final String AW_WATCHER = "aw-watcher-"; 48 | 49 | private static final String IDE_NAME = ApplicationInfo.getInstance().getVersionName().toLowerCase().replaceAll("\\s", "-"); 50 | private static final String IDE_VERSION = ApplicationInfo.getInstance().getFullVersion(); 51 | public static final int HEARTBEAT_PULSETIME = 20; 52 | public static final int CHECK_CONNEXION_DELAY = 10; 53 | private static final DefaultApi API_CLIENT = new DefaultApi(); 54 | public static final int CONNECTION_TIMEOUT = 200000; 55 | public static final int READ_WRITE_TIMEOUT = 100000; 56 | private static Bucket bucket; 57 | private static final ScheduledExecutorService connexionScheduler = Executors.newScheduledThreadPool(1); 58 | private static ScheduledFuture scheduledConnexion; 59 | private static boolean connexionFailedMessageAlreadySend = false; 60 | private static boolean connexionLost = false; 61 | private static boolean initialCheckDone = false; 62 | private static MessageBusConnection connection; 63 | private static ReportActivity instance; 64 | 65 | 66 | private ReportActivity() { 67 | LOG.info("Initializing ActivityWatcher plugin : Start"); 68 | setupConnexionToApi(); 69 | setupEventListeners(); 70 | instance = this; 71 | } 72 | 73 | private static void setupEventListeners() { 74 | ApplicationManager.getApplication().invokeLater(() -> { 75 | // Auto save 76 | MessageBus bus = ApplicationManager.getApplication().getMessageBus(); 77 | connection = bus.connect(); 78 | connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new RASaveListener()); 79 | // Switch document or in document 80 | EditorFactory.getInstance().getEventMulticaster().addDocumentListener(new RADocumentListener(), instance); 81 | EditorFactory.getInstance().getEventMulticaster().addEditorMouseListener(new RAEditorMouseListener(), instance); 82 | EditorFactory.getInstance().getEventMulticaster().addVisibleAreaListener(new RAVisibleAreaListener(), instance); 83 | }); 84 | } 85 | 86 | public static void sendHeartBeat(VirtualFile file, Document document) { 87 | sendHeartBeat(file, getProject(document)); 88 | } 89 | 90 | private static Project getProject(Document document) { 91 | Editor[] editors = EditorFactory.getInstance().getEditors(document); 92 | if (editors.length > 0) { 93 | return editors[0].getProject(); 94 | } 95 | return null; 96 | } 97 | 98 | public static void sendHeartBeat(VirtualFile file, Project project) { 99 | if (bucket != null && isAppActive() && !connexionLost) { 100 | if (project == null || !project.isInitialized()) { 101 | return; 102 | } 103 | if (file == null) { 104 | return; 105 | } 106 | 107 | String language = file.getFileType().getDisplayName(); 108 | if (file.getFileType() instanceof LanguageFileType) { 109 | language = ((LanguageFileType) file.getFileType()).getLanguage().getDisplayName(); 110 | } 111 | 112 | HeartBeatData.HeartBeatDataBuilder dataBuilder = HeartBeatData.builder().projectPath(project.getPresentableUrl()).editorVersion(IDE_VERSION).editor(IDE_NAME) 113 | //.eventType(classz.getName()) disabled for heartbeat because data change and create multiples of event 114 | .file(file.getPresentableName()).fileFullPath(file.getPath()).project(project.getName()).language(language); 115 | GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(project); 116 | repositoryManager.getRepositories().stream().findFirst().ifPresent(r -> { 117 | dataBuilder.branch(r.getCurrentBranchName()); 118 | dataBuilder.commit(r.getCurrentRevision()); 119 | dataBuilder.state(r.getState().name()); 120 | r.getInfo().getRemotes().stream().findFirst().ifPresent(gitRemote -> dataBuilder.sourceUrl(gitRemote.getFirstUrl())); 121 | }); 122 | 123 | HeartBeatData data = dataBuilder.build(); 124 | Event event = new Event().data(data).timestamp(OffsetDateTime.now()); 125 | 126 | try { 127 | API_CLIENT.postHeartbeatResourceAsync(bucket.getId(), event, "" + HEARTBEAT_PULSETIME, new ApiCallback<>() { 128 | @Override 129 | public void onFailure(ApiException e, int statusCode, Map> responseHeaders) { 130 | LOG.warn("Unable to send heartbeat:", e); 131 | ReportActivity.connexionLost(); 132 | } 133 | 134 | @Override 135 | public void onSuccess(Void result, int statusCode, Map> responseHeaders) { 136 | // nothing to do 137 | } 138 | 139 | @Override 140 | public void onUploadProgress(long bytesWritten, long contentLength, boolean done) { 141 | // nothing to do 142 | } 143 | 144 | @Override 145 | public void onDownloadProgress(long bytesRead, long contentLength, boolean done) { 146 | // nothing to do 147 | } 148 | }); 149 | } catch (Exception e) { 150 | LOG.warn("Unable to send heartbeat:", e); 151 | ReportActivity.connexionLost(); 152 | } 153 | } 154 | } 155 | 156 | private static void initBucket() { 157 | String bucketClientNamePrefix = AW_WATCHER + IDE_NAME; 158 | String hostname; 159 | try { 160 | hostname = InetAddress.getLocalHost().getHostName(); 161 | } catch (Exception exp) { 162 | LOG.warn("Unable to get hostname:", exp); 163 | hostname = "unknown"; 164 | } 165 | 166 | try { 167 | String finalHostname = hostname; 168 | API_CLIENT.getBucketResourceAsync(bucketClientNamePrefix + "_" + hostname, new ApiCallback<>() { 169 | 170 | @Override 171 | public void onSuccess(Bucket result, int statusCode, Map> responseHeaders) { 172 | LOG.info("Bucket found"); 173 | bucket = result; 174 | initialCheckDone = true; 175 | } 176 | 177 | @Override 178 | public void onFailure(ApiException e, int statusCode, Map> responseHeaders) { 179 | LOG.info("Bucket not found, create it"); 180 | CreateBucket nb = new CreateBucket(); 181 | nb.setClient(bucketClientNamePrefix); 182 | nb.setHostname(finalHostname); 183 | nb.setType(TYPE); 184 | try { 185 | API_CLIENT.postBucketResourceAsync(bucketClientNamePrefix + "_" + finalHostname, nb, new ApiCallback<>() { 186 | 187 | @Override 188 | public void onSuccess(Void result, int statusCode, Map> responseHeaders) { 189 | LOG.info("Bucket created"); 190 | try { 191 | API_CLIENT.getBucketResourceAsync(bucketClientNamePrefix + "_" + finalHostname, new ApiCallback<>() { 192 | @Override 193 | public void onSuccess(Bucket result, int statusCode, Map> responseHeaders) { 194 | LOG.info("Bucket found"); 195 | bucket = result; 196 | initialCheckDone = true; 197 | } 198 | 199 | @Override 200 | public void onFailure(ApiException e, int statusCode, Map> responseHeaders) { 201 | LOG.warn("Unable to init bucket:", e); 202 | connexionLost = true; 203 | } 204 | 205 | 206 | @Override 207 | public void onUploadProgress(long bytesWritten, long contentLength, boolean done) { 208 | // nothing to do 209 | } 210 | 211 | @Override 212 | public void onDownloadProgress(long bytesRead, long contentLength, boolean done) { 213 | // nothing to do 214 | } 215 | }); 216 | } catch (ApiException ex) { 217 | LOG.warn("Unable to init bucket:", ex); 218 | } 219 | } 220 | 221 | @Override 222 | public void onFailure(ApiException e, int statusCode, Map> responseHeaders) { 223 | LOG.warn("Unable to create bucket:", e); 224 | initialCheckDone = true; 225 | } 226 | 227 | @Override 228 | public void onUploadProgress(long bytesWritten, long contentLength, boolean done) { 229 | // nothing to do 230 | } 231 | 232 | @Override 233 | public void onDownloadProgress(long bytesRead, long contentLength, boolean done) { 234 | // nothing to do 235 | } 236 | } 237 | 238 | ); 239 | } catch (Exception expB) { 240 | LOG.warn("Unable to create bucket:", expB); 241 | initialCheckDone = true; 242 | } 243 | } 244 | 245 | 246 | @Override 247 | public void onUploadProgress(long bytesWritten, long contentLength, boolean done) { 248 | // nothing to do 249 | } 250 | 251 | @Override 252 | public void onDownloadProgress(long bytesRead, long contentLength, boolean done) { 253 | // nothing to do 254 | } 255 | }); 256 | } catch (Exception exp) { 257 | LOG.warn("Unable to init bucket:", exp); 258 | connexionLost = true; 259 | } 260 | } 261 | 262 | private static void setupConnexionToApi() { 263 | API_CLIENT.getApiClient().setConnectTimeout(CONNECTION_TIMEOUT); 264 | API_CLIENT.getApiClient().setReadTimeout(READ_WRITE_TIMEOUT); 265 | API_CLIENT.getApiClient().setWriteTimeout(READ_WRITE_TIMEOUT); 266 | final Runnable handler = () -> { 267 | if (bucket == null) { 268 | initBucket(); 269 | } 270 | 271 | if (initialCheckDone && bucket == null && !connexionFailedMessageAlreadySend) { 272 | connexionLost(); 273 | } 274 | 275 | if (initialCheckDone && bucket != null && connexionLost) { 276 | connexionResume(); 277 | } 278 | }; 279 | scheduledConnexion = connexionScheduler.scheduleWithFixedDelay(handler, 0, CHECK_CONNEXION_DELAY, TimeUnit.SECONDS); 280 | } 281 | 282 | private static boolean isAppActive() { 283 | return KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow() != null; 284 | } 285 | 286 | @Override 287 | public void dispose() { 288 | try { 289 | connection.disconnect(); 290 | } catch (Exception e) { 291 | LOG.warn("Unable to disconnect to message bus:", e); 292 | } 293 | try { 294 | scheduledConnexion.cancel(true); 295 | } catch (Exception e) { 296 | LOG.warn("Unable to cancel schedulers:", e); 297 | } 298 | } 299 | 300 | private static synchronized void connexionResume() { 301 | if (connexionLost) { 302 | connexionLost = false; 303 | connexionFailedMessageAlreadySend = false; 304 | Notifications.Bus.notify(new Notification(ReportActivity.ACTIVITY_WATCHER, ReportActivity.ACTIVITY_WATCHER, "Activity Watcher Server is back!", NotificationType.INFORMATION)); 305 | } 306 | } 307 | 308 | protected static synchronized void connexionLost() { 309 | if (!connexionFailedMessageAlreadySend) { 310 | connexionFailedMessageAlreadySend = true; 311 | Notifications.Bus.notify(new Notification(ReportActivity.ACTIVITY_WATCHER, ReportActivity.ACTIVITY_WATCHER, "Activity Watcher Server connection lost?\nWill try to re-send events when connection back.", NotificationType.WARNING)); 312 | } 313 | connexionLost = true; 314 | bucket = null; 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/main/java/fr/mary/olivier/aw/watcher/listener/RADocumentListener.java: -------------------------------------------------------------------------------- 1 | package fr.mary.olivier.aw.watcher.listener; 2 | 3 | 4 | import com.intellij.openapi.editor.Document; 5 | import com.intellij.openapi.editor.event.DocumentEvent; 6 | import com.intellij.openapi.editor.event.DocumentListener; 7 | import com.intellij.openapi.fileEditor.FileDocumentManager; 8 | import com.intellij.openapi.vfs.VirtualFile; 9 | import fr.mary.olivier.aw.watcher.ReportActivity; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public class RADocumentListener implements DocumentListener { 13 | @Override 14 | public void beforeDocumentChange(@NotNull DocumentEvent documentEvent) { 15 | // no action 16 | } 17 | 18 | @Override 19 | public void documentChanged(@NotNull DocumentEvent documentEvent) { 20 | Document document = documentEvent.getDocument(); 21 | VirtualFile file = FileDocumentManager.getInstance().getFile(document); 22 | ReportActivity.sendHeartBeat(file, document); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/fr/mary/olivier/aw/watcher/listener/RAEditorMouseListener.java: -------------------------------------------------------------------------------- 1 | package fr.mary.olivier.aw.watcher.listener; 2 | 3 | import com.intellij.openapi.editor.Editor; 4 | import com.intellij.openapi.editor.event.EditorMouseEvent; 5 | import com.intellij.openapi.editor.event.EditorMouseListener; 6 | import com.intellij.openapi.fileEditor.FileDocumentManager; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.vfs.VirtualFile; 9 | import fr.mary.olivier.aw.watcher.ReportActivity; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public class RAEditorMouseListener implements EditorMouseListener { 13 | @Override 14 | public void mousePressed(@NotNull EditorMouseEvent editorMouseEvent) { 15 | Editor editor = editorMouseEvent.getEditor(); 16 | VirtualFile file = FileDocumentManager.getInstance().getFile(editor.getDocument()); 17 | Project project = editor.getProject(); 18 | ReportActivity.sendHeartBeat(file, project); 19 | } 20 | 21 | @Override 22 | public void mouseClicked(@NotNull EditorMouseEvent editorMouseEvent) { 23 | // no action 24 | } 25 | 26 | @Override 27 | public void mouseReleased(@NotNull EditorMouseEvent editorMouseEvent) { 28 | // no action 29 | } 30 | 31 | @Override 32 | public void mouseEntered(@NotNull EditorMouseEvent editorMouseEvent) { 33 | // no action 34 | } 35 | 36 | @Override 37 | public void mouseExited(@NotNull EditorMouseEvent editorMouseEvent) { 38 | // no action 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/fr/mary/olivier/aw/watcher/listener/RASaveListener.java: -------------------------------------------------------------------------------- 1 | package fr.mary.olivier.aw.watcher.listener; 2 | 3 | import com.intellij.openapi.editor.Document; 4 | import com.intellij.openapi.fileEditor.FileDocumentManager; 5 | import com.intellij.openapi.fileEditor.FileDocumentManagerListener; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import fr.mary.olivier.aw.watcher.ReportActivity; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public class RASaveListener implements FileDocumentManagerListener { 11 | 12 | @Override 13 | public void beforeDocumentSaving(@NotNull Document document) { 14 | VirtualFile file = FileDocumentManager.getInstance().getFile(document); 15 | ReportActivity.sendHeartBeat(file, document); 16 | } 17 | 18 | @Override 19 | public void beforeAllDocumentsSaving() { 20 | // no action 21 | } 22 | 23 | @Override 24 | public void beforeFileContentReload(@NotNull VirtualFile file, @NotNull Document document) { 25 | // no action 26 | } 27 | 28 | @Override 29 | public void fileWithNoDocumentChanged(@NotNull VirtualFile file) { 30 | // no action 31 | } 32 | 33 | @Override 34 | public void fileContentReloaded(@NotNull VirtualFile file, @NotNull Document document) { 35 | // no action 36 | } 37 | 38 | @Override 39 | public void fileContentLoaded(@NotNull VirtualFile file, @NotNull Document document) { 40 | // no action 41 | } 42 | 43 | @Override 44 | public void unsavedDocumentsDropped() { 45 | // no action 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/fr/mary/olivier/aw/watcher/listener/RAVisibleAreaListener.java: -------------------------------------------------------------------------------- 1 | package fr.mary.olivier.aw.watcher.listener; 2 | 3 | import com.intellij.openapi.editor.Editor; 4 | import com.intellij.openapi.editor.event.VisibleAreaEvent; 5 | import com.intellij.openapi.editor.event.VisibleAreaListener; 6 | import com.intellij.openapi.fileEditor.FileDocumentManager; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.vfs.VirtualFile; 9 | import fr.mary.olivier.aw.watcher.ReportActivity; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public class RAVisibleAreaListener implements VisibleAreaListener { 13 | @Override 14 | public void visibleAreaChanged(@NotNull VisibleAreaEvent visibleAreaEvent) { 15 | Editor editor = visibleAreaEvent.getEditor(); 16 | VirtualFile file = FileDocumentManager.getInstance().getFile(editor.getDocument()); 17 | Project project = editor.getProject(); 18 | ReportActivity.sendHeartBeat(file, project); 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | fr.mary.olivier.aw-watcher 3 | Activity Watcher 4 | OlivierMARY 5 | 2.0.9 6 | 7 | 10 | 11 | 12 | 13 | com.intellij.modules.lang 14 | com.intellij.modules.platform 15 | Git4Idea 16 | 17 | 18 | 19 | 20 | 21 | fr.mary.olivier.aw.watcher.ReportActivity 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/swagger.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | basePath: /api 3 | schemes: 4 | - http 5 | host: localhost:5600 6 | paths: 7 | /0/buckets/: 8 | get: 9 | responses: 10 | '200': 11 | description: Success 12 | summary: 'Get dict {bucket_name: Bucket} of all buckets' 13 | operationId: get_buckets_resource 14 | tags: 15 | - default 16 | '/0/buckets/{bucket_id}': 17 | parameters: 18 | - name: bucket_id 19 | in: path 20 | required: true 21 | type: string 22 | get: 23 | responses: 24 | '200': 25 | description: Success 26 | schema: 27 | $ref: '#/definitions/Bucket' 28 | summary: Get metadata about bucket 29 | operationId: get_bucket_resource 30 | tags: 31 | - default 32 | post: 33 | responses: 34 | '200': 35 | description: Success 36 | summary: Create bucket 37 | description: 'Returns True if successful, otherwise false if a bucket with the given ID already existed.' 38 | operationId: post_bucket_resource 39 | parameters: 40 | - name: payload 41 | required: true 42 | in: body 43 | schema: 44 | $ref: '#/definitions/CreateBucket' 45 | tags: 46 | - default 47 | delete: 48 | responses: 49 | '200': 50 | description: Success 51 | summary: Delete a bucket 52 | operationId: delete_bucket_resource 53 | parameters: 54 | - in: query 55 | description: Needs to be =1 to delete a bucket it non-testing mode 56 | name: force 57 | type: string 58 | tags: 59 | - default 60 | '/0/buckets/{bucket_id}/events': 61 | parameters: 62 | - name: bucket_id 63 | in: path 64 | required: true 65 | type: string 66 | get: 67 | responses: 68 | '200': 69 | description: Success 70 | schema: 71 | $ref: '#/definitions/Event' 72 | summary: Get events from a bucket 73 | operationId: get_events_resource 74 | parameters: 75 | - in: query 76 | description: End date of events 77 | name: end 78 | type: string 79 | - in: query 80 | description: Start date of events 81 | name: start 82 | type: string 83 | - in: query 84 | description: the maximum number of requests to get 85 | name: limit 86 | type: string 87 | tags: 88 | - default 89 | post: 90 | responses: 91 | '200': 92 | description: Success 93 | summary: Create events for a bucket 94 | description: |- 95 | Can handle both single events and multiple ones. 96 | 97 | Returns the inserted event when a single event was inserted, otherwise None. 98 | operationId: post_events_resource 99 | parameters: 100 | - name: payload 101 | required: true 102 | in: body 103 | schema: 104 | $ref: '#/definitions/Event' 105 | tags: 106 | - default 107 | '/0/buckets/{bucket_id}/events/count': 108 | parameters: 109 | - name: bucket_id 110 | in: path 111 | required: true 112 | type: string 113 | get: 114 | responses: 115 | '200': 116 | description: Success 117 | schema: 118 | type: integer 119 | summary: Get eventcount from a bucket 120 | operationId: get_event_count_resource 121 | parameters: 122 | - in: query 123 | description: End date of eventcount 124 | name: end 125 | type: string 126 | - in: query 127 | description: Start date of eventcount 128 | name: start 129 | type: string 130 | tags: 131 | - default 132 | '/0/buckets/{bucket_id}/events/{event_id}': 133 | parameters: 134 | - name: bucket_id 135 | in: path 136 | required: true 137 | type: string 138 | - name: event_id 139 | in: path 140 | required: true 141 | type: string 142 | delete: 143 | responses: 144 | '200': 145 | description: Success 146 | summary: Delete a single event from a bucket 147 | operationId: delete_event_resource 148 | tags: 149 | - default 150 | '/0/buckets/{bucket_id}/export': 151 | parameters: 152 | - name: bucket_id 153 | in: path 154 | required: true 155 | type: string 156 | get: 157 | responses: 158 | '200': 159 | description: Success 160 | schema: 161 | $ref: '#/definitions/Export' 162 | summary: 'Export a bucket to a dataformat consistent across versions, including all events in it' 163 | operationId: get_bucket_export_resource 164 | tags: 165 | - default 166 | '/0/buckets/{bucket_id}/heartbeat': 167 | parameters: 168 | - name: bucket_id 169 | in: path 170 | required: true 171 | type: string 172 | post: 173 | responses: 174 | '200': 175 | description: Success 176 | summary: Heartbeats are useful when implementing watchers that simply keep 177 | description: |- 178 | track of a state, how long it's in that state and when it changes. 179 | A single heartbeat always has a duration of zero. 180 | 181 | If the heartbeat was identical to the last (apart from timestamp), then the last event has its duration updated. 182 | If the heartbeat differed, then a new event is created. 183 | 184 | Such as: 185 | - Active application and window title 186 | - Example: aw-watcher-window 187 | - Currently open document/browser tab/playing song 188 | - Example: wakatime 189 | - Example: aw-watcher-web 190 | - Example: aw-watcher-spotify 191 | - Is the user active/inactive? 192 | Send an event on some interval indicating if the user is active or not. 193 | - Example: aw-watcher-afk 194 | 195 | Inspired by: https://wakatime.com/developers#heartbeats 196 | operationId: post_heartbeat_resource 197 | parameters: 198 | - name: payload 199 | required: true 200 | in: body 201 | schema: 202 | $ref: '#/definitions/Event' 203 | - in: query 204 | description: Largest timewindow allowed between heartbeats for them to merge 205 | name: pulsetime 206 | type: string 207 | tags: 208 | - default 209 | /0/export: 210 | get: 211 | responses: 212 | '200': 213 | description: Success 214 | schema: 215 | $ref: '#/definitions/Export' 216 | summary: Exports all buckets and their events to a format consistent across versions 217 | operationId: get_export_all_resource 218 | tags: 219 | - default 220 | /0/import: 221 | post: 222 | responses: 223 | '200': 224 | description: Success 225 | operationId: post_import_all_resource 226 | parameters: 227 | - name: payload 228 | required: true 229 | in: body 230 | schema: 231 | $ref: '#/definitions/Export' 232 | tags: 233 | - default 234 | /0/info: 235 | get: 236 | responses: 237 | '200': 238 | description: Success 239 | schema: 240 | $ref: '#/definitions/Info' 241 | summary: Get server info 242 | operationId: get_info_resource 243 | parameters: 244 | - name: X-Fields 245 | in: header 246 | type: string 247 | format: mask 248 | description: An optional fields mask 249 | tags: 250 | - default 251 | /0/log: 252 | get: 253 | responses: 254 | '200': 255 | description: Success 256 | summary: Get the server log in json format 257 | operationId: get_log_resource 258 | tags: 259 | - default 260 | /0/query/: 261 | post: 262 | responses: 263 | '200': 264 | description: Success 265 | operationId: post_query_resource 266 | parameters: 267 | - name: payload 268 | required: true 269 | in: body 270 | schema: 271 | $ref: '#/definitions/Query' 272 | - in: query 273 | description: Name of the query (required if using cache) 274 | name: name 275 | type: string 276 | tags: 277 | - default 278 | info: 279 | title: API 280 | version: '1.0' 281 | produces: 282 | - application/json 283 | consumes: 284 | - application/json 285 | tags: 286 | - name: default 287 | description: Default namespace 288 | definitions: 289 | Info: 290 | properties: 291 | hostname: 292 | type: string 293 | version: 294 | type: string 295 | testing: 296 | type: boolean 297 | type: object 298 | CreateBucket: 299 | required: 300 | - client 301 | - hostname 302 | - type 303 | properties: 304 | client: 305 | type: string 306 | type: 307 | type: string 308 | hostname: 309 | type: string 310 | type: object 311 | Bucket: 312 | title: Bucket 313 | description: The Bucket model that is used in ActivityWatch 314 | type: object 315 | required: 316 | - id 317 | - type 318 | - client 319 | - hostname 320 | properties: 321 | id: 322 | description: The unique id for the bucket 323 | type: string 324 | name: 325 | description: The readable and renameable name for the bucket 326 | type: string 327 | type: 328 | description: The event type 329 | type: string 330 | client: 331 | description: The name of the client that is reporting to the bucket 332 | type: string 333 | hostname: 334 | description: The hostname of the machine on which the client is running 335 | type: string 336 | created: 337 | description: The creation datetime of the bucket 338 | type: string 339 | format: date-time 340 | data: 341 | description: '' 342 | type: object 343 | metadata: 344 | description: '' 345 | type: object 346 | last_updated: 347 | description: The last updated datetime of the bucket 348 | type: string 349 | format: date-time 350 | events: 351 | type: array 352 | items: 353 | $ref: '#/definitions/Event' 354 | Event: 355 | title: Event 356 | description: The Event model that is used in ActivityWatch 357 | type: object 358 | required: 359 | - timestamp 360 | properties: 361 | timestamp: 362 | type: string 363 | format: date-time 364 | duration: 365 | type: number 366 | data: 367 | type: object 368 | Query: 369 | required: 370 | - query 371 | - timeperiods 372 | properties: 373 | timeperiods: 374 | type: array 375 | description: List of periods to query 376 | items: 377 | type: string 378 | query: 379 | type: array 380 | description: String list of query statements 381 | items: 382 | type: string 383 | type: object 384 | Export: 385 | title: Export 386 | description: The Export model that is used by ActivityWatch 387 | type: object 388 | properties: 389 | buckets: 390 | type: array 391 | items: 392 | $ref: '#/definitions/Bucket' 393 | responses: 394 | ParseError: 395 | description: When a mask can't be parsed 396 | MaskError: 397 | description: When any error occurs on mask 398 | --------------------------------------------------------------------------------