├── update-site ├── content.jar ├── artifacts.jar ├── plugins │ ├── com.wakatime.eclipse.plugin_1.0.10.jar │ ├── com.wakatime.eclipse.plugin_1.0.2.jar │ ├── com.wakatime.eclipse.plugin_1.0.3.jar │ ├── com.wakatime.eclipse.plugin_1.0.4.jar │ ├── com.wakatime.eclipse.plugin_1.0.5.jar │ ├── com.wakatime.eclipse.plugin_1.0.6.jar │ ├── com.wakatime.eclipse.plugin_1.0.7.jar │ ├── com.wakatime.eclipse.plugin_1.0.8.jar │ ├── com.wakatime.eclipse.plugin_1.0.9.jar │ ├── com.wakatime.eclipse.plugin_1.1.0.jar │ ├── com.wakatime.eclipse.plugin_2.0.0.jar │ ├── com.wakatime.eclipse.plugin_2.0.1.jar │ ├── com.wakatime.eclipse.plugin_2.0.2.jar │ ├── com.wakatime.eclipse.plugin_2.0.3.jar │ ├── com.wakatime.eclipse.plugin_2.0.4.jar │ ├── com.wakatime.eclipse.plugin_2.0.5.jar │ ├── com.wakatime.eclipse.plugin_2.0.6.jar │ ├── com.wakatime.eclipse.plugin_3.0.0.jar │ ├── com.wakatime.eclipse.plugin_3.0.1.jar │ ├── com.wakatime.eclipse.plugin_3.0.2.jar │ ├── com.wakatime.eclipse.plugin_3.0.3.jar │ ├── com.wakatime.eclipse.plugin_3.0.4.jar │ ├── com.wakatime.eclipse.plugin_3.0.5.jar │ ├── com.wakatime.eclipse.plugin_3.0.8.jar │ ├── com.wakatime.eclipse.plugin_3.0.9.jar │ ├── com.wakatime.eclipse.plugin_4.0.0.jar │ ├── com.wakatime.eclipse.plugin_4.0.1.jar │ ├── com.wakatime.eclipse.plugin_4.0.2.jar │ ├── com.wakatime.eclipse.plugin_4.1.0.jar │ ├── com.wakatime.eclipse.plugin_4.2.0.jar │ ├── com.wakatime.eclipse.plugin_4.2.1.jar │ ├── com.wakatime.eclipse.plugin_5.0.0.jar │ ├── com.wakatime.eclipse.plugin_5.0.1.jar │ ├── com.wakatime.eclipse.plugin_5.0.2.jar │ └── com.wakatime.eclipse.plugin_5.1.0.jar ├── features │ ├── com.wakatime.eclipse.feature_1.0.10.jar │ ├── com.wakatime.eclipse.feature_1.0.2.jar │ ├── com.wakatime.eclipse.feature_1.0.3.jar │ ├── com.wakatime.eclipse.feature_1.0.4.jar │ ├── com.wakatime.eclipse.feature_1.0.5.jar │ ├── com.wakatime.eclipse.feature_1.0.6.jar │ ├── com.wakatime.eclipse.feature_1.0.7.jar │ ├── com.wakatime.eclipse.feature_1.0.8.jar │ ├── com.wakatime.eclipse.feature_1.0.9.jar │ ├── com.wakatime.eclipse.feature_1.1.0.jar │ ├── com.wakatime.eclipse.feature_2.0.0.jar │ ├── com.wakatime.eclipse.feature_2.0.1.jar │ ├── com.wakatime.eclipse.feature_2.0.2.jar │ ├── com.wakatime.eclipse.feature_2.0.3.jar │ ├── com.wakatime.eclipse.feature_2.0.4.jar │ ├── com.wakatime.eclipse.feature_2.0.5.jar │ ├── com.wakatime.eclipse.feature_2.0.6.jar │ ├── com.wakatime.eclipse.feature_3.0.0.jar │ ├── com.wakatime.eclipse.feature_3.0.1.jar │ ├── com.wakatime.eclipse.feature_3.0.2.jar │ ├── com.wakatime.eclipse.feature_3.0.3.jar │ ├── com.wakatime.eclipse.feature_3.0.4.jar │ ├── com.wakatime.eclipse.feature_3.0.5.jar │ ├── com.wakatime.eclipse.feature_3.0.8.jar │ ├── com.wakatime.eclipse.feature_3.0.9.jar │ ├── com.wakatime.eclipse.feature_4.0.0.jar │ ├── com.wakatime.eclipse.feature_4.0.1.jar │ ├── com.wakatime.eclipse.feature_4.0.2.jar │ ├── com.wakatime.eclipse.feature_4.1.0.jar │ ├── com.wakatime.eclipse.feature_4.2.0.jar │ ├── com.wakatime.eclipse.feature_4.2.1.jar │ ├── com.wakatime.eclipse.feature_5.0.0.jar │ ├── com.wakatime.eclipse.feature_5.0.1.jar │ ├── com.wakatime.eclipse.feature_5.0.2.jar │ └── com.wakatime.eclipse.feature_5.1.0.jar ├── .project ├── site.xml └── category.xml ├── com.wakatime.eclipse.feature ├── build.properties ├── category.xml ├── .project └── feature.xml ├── com.wakatime.eclipse.plugin ├── build.properties ├── .classpath ├── META-INF │ └── MANIFEST.MF ├── .project ├── src │ └── com │ │ └── wakatime │ │ └── eclipse │ │ └── plugin │ │ ├── CustomCaretListener.java │ │ ├── LocalSSLTrustManager.java │ │ ├── CustomDocumentListener.java │ │ ├── CustomMouseListener.java │ │ ├── Debouncer.java │ │ ├── MenuHandler.java │ │ ├── Logger.java │ │ ├── CustomEditorListener.java │ │ ├── CustomExecutionListener.java │ │ ├── ConfigFile.java │ │ ├── Heartbeat.java │ │ ├── WakaTime.java │ │ └── Dependencies.java └── plugin.xml ├── .gitignore ├── .project ├── AUTHORS ├── README.md └── LICENSE /update-site/content.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/content.jar -------------------------------------------------------------------------------- /com.wakatime.eclipse.feature/build.properties: -------------------------------------------------------------------------------- 1 | bin.includes = feature.xml,\ 2 | category.xml 3 | -------------------------------------------------------------------------------- /update-site/artifacts.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/artifacts.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_1.0.10.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_1.0.10.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_1.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_1.0.2.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_1.0.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_1.0.3.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_1.0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_1.0.4.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_1.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_1.0.5.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_1.0.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_1.0.6.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_1.0.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_1.0.7.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_1.0.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_1.0.8.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_1.0.9.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_1.0.9.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_1.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_1.1.0.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_2.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_2.0.0.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_2.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_2.0.1.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_2.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_2.0.2.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_2.0.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_2.0.3.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_2.0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_2.0.4.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_2.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_2.0.5.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_2.0.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_2.0.6.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_3.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_3.0.0.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_3.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_3.0.1.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_3.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_3.0.2.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_3.0.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_3.0.3.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_3.0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_3.0.4.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_3.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_3.0.5.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_3.0.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_3.0.8.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_3.0.9.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_3.0.9.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_4.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_4.0.0.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_4.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_4.0.1.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_4.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_4.0.2.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_4.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_4.1.0.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_4.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_4.2.0.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_4.2.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_4.2.1.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_5.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_5.0.0.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_5.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_5.0.1.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_5.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_5.0.2.jar -------------------------------------------------------------------------------- /update-site/plugins/com.wakatime.eclipse.plugin_5.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/plugins/com.wakatime.eclipse.plugin_5.1.0.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_1.0.10.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_1.0.10.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_1.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_1.0.2.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_1.0.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_1.0.3.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_1.0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_1.0.4.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_1.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_1.0.5.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_1.0.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_1.0.6.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_1.0.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_1.0.7.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_1.0.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_1.0.8.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_1.0.9.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_1.0.9.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_1.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_1.1.0.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_2.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_2.0.0.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_2.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_2.0.1.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_2.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_2.0.2.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_2.0.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_2.0.3.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_2.0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_2.0.4.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_2.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_2.0.5.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_2.0.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_2.0.6.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_3.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_3.0.0.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_3.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_3.0.1.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_3.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_3.0.2.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_3.0.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_3.0.3.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_3.0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_3.0.4.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_3.0.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_3.0.5.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_3.0.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_3.0.8.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_3.0.9.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_3.0.9.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_4.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_4.0.0.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_4.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_4.0.1.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_4.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_4.0.2.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_4.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_4.1.0.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_4.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_4.2.0.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_4.2.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_4.2.1.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_5.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_5.0.0.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_5.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_5.0.1.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_5.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_5.0.2.jar -------------------------------------------------------------------------------- /update-site/features/com.wakatime.eclipse.feature_5.1.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wakatime/eclipse-wakatime/HEAD/update-site/features/com.wakatime.eclipse.feature_5.1.0.jar -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/build.properties: -------------------------------------------------------------------------------- 1 | source.. = src/ 2 | output.. = bin/ 3 | bin.includes = plugin.xml,\ 4 | .,\ 5 | src/,\ 6 | META-INF/ 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.class 3 | 4 | # Package Files # 5 | *.war 6 | *.ear 7 | 8 | # Eclipse meta data 9 | .metadata 10 | .settings/ 11 | local.properties 12 | bin/ 13 | dependencies/ 14 | Local/ 15 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | eclipse-wakatime 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | WakaTime is written and maintained by Alan Hamlett and 2 | various contributors: 3 | 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | - Alan Hamlett 9 | 10 | 11 | Patches and Suggestions 12 | ----------------------- 13 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.feature/category.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Fully automatic time tracking. 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /update-site/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | update-site 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.pde.UpdateSiteBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.pde.UpdateSiteNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.feature/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.wakatime.eclipse.feature 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.pde.FeatureBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.pde.FeatureNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Bundle-ManifestVersion: 2 3 | Bundle-Name: WakaTime 4 | Bundle-SymbolicName: com.wakatime.eclipse.plugin;singleton:=true 5 | Bundle-Version: 5.1.0 6 | Bundle-Activator: com.wakatime.eclipse.plugin.WakaTime 7 | Bundle-Vendor: WAKATIME 8 | Require-Bundle: org.eclipse.ui, 9 | org.eclipse.core.runtime, 10 | org.eclipse.core.resources, 11 | org.eclipse.ui.ide, 12 | org.eclipse.jface.text, 13 | org.apache.httpcomponents.httpclient, 14 | org.apache.httpcomponents.httpcore, 15 | org.eclipse.ui.workbench.texteditor 16 | Bundle-RequiredExecutionEnvironment: JavaSE-1.6 17 | Bundle-ActivationPolicy: lazy 18 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.wakatime.eclipse.plugin 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.pde.ManifestBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.pde.SchemaBuilder 20 | 21 | 22 | 23 | 24 | 25 | org.eclipse.pde.PluginNature 26 | org.eclipse.jdt.core.javanature 27 | 28 | 29 | -------------------------------------------------------------------------------- /update-site/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | Fully automatic time tracking for Eclipse. 6 | 7 | 8 | 9 | 10 | 11 | 12 | Metrics, insights, and time tracking automatically generated from your programming activity. 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /update-site/category.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fully automatic time tracking for Eclipse. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Metrics, insights, and time tracking automatically generated from your programming activity. 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/CustomCaretListener.java: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | File: CustomCaretListener.java 3 | Description: Automatic time tracking for Eclipse. 4 | Maintainer: WakaTime 5 | License: BSD, see LICENSE for more details. 6 | Website: https://wakatime.com/ 7 | ===========================================================*/ 8 | 9 | 10 | package com.wakatime.eclipse.plugin; 11 | 12 | import org.eclipse.swt.custom.CaretEvent; 13 | import org.eclipse.swt.custom.CaretListener; 14 | 15 | public class CustomCaretListener implements CaretListener { 16 | 17 | @Override 18 | public void caretMoved(CaretEvent event) { 19 | // Logger.debug("CustomCaretListener.caretMoved"); 20 | 21 | Heartbeat heartbeat = WakaTime.getHeartbeat(null, false); 22 | WakaTime.processHeartbeat(heartbeat); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/LocalSSLTrustManager.java: -------------------------------------------------------------------------------- 1 | package com.wakatime.eclipse.plugin; 2 | 3 | import java.security.cert.CertificateException; 4 | import java.security.cert.X509Certificate; 5 | 6 | import javax.net.ssl.X509TrustManager; 7 | 8 | class LocalSSLTrustManager implements X509TrustManager { 9 | @Override 10 | public void checkClientTrusted(final X509Certificate[] arg0, 11 | final String arg1) throws CertificateException { 12 | // TODO Auto-generated method stub 13 | 14 | } 15 | 16 | @Override 17 | public void checkServerTrusted(final X509Certificate[] arg0, 18 | final String arg1) throws CertificateException { 19 | // TODO Auto-generated method stub 20 | 21 | } 22 | 23 | @Override 24 | public final X509Certificate[] getAcceptedIssuers() { 25 | // TODO Auto-generated method stub 26 | return null; 27 | } 28 | } -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/CustomDocumentListener.java: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | File: CustomDocumentListener.java 3 | Description: Automatic time tracking for Eclipse. 4 | Maintainer: WakaTime 5 | License: BSD, see LICENSE for more details. 6 | Website: https://wakatime.com/ 7 | ===========================================================*/ 8 | 9 | 10 | package com.wakatime.eclipse.plugin; 11 | 12 | import org.eclipse.jface.text.DocumentEvent; 13 | import org.eclipse.jface.text.IDocumentListener; 14 | 15 | public class CustomDocumentListener implements IDocumentListener { 16 | 17 | @Override 18 | public void documentAboutToBeChanged(DocumentEvent event) { 19 | // noop 20 | } 21 | 22 | @Override 23 | public void documentChanged(DocumentEvent event) { 24 | Heartbeat heartbeat = WakaTime.getHeartbeat(null, false); 25 | WakaTime.processHeartbeat(heartbeat); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/CustomMouseListener.java: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | File: CustomCaretListener.java 3 | Description: Automatic time tracking for Eclipse. 4 | Maintainer: WakaTime 5 | License: BSD, see LICENSE for more details. 6 | Website: https://wakatime.com/ 7 | ===========================================================*/ 8 | 9 | 10 | package com.wakatime.eclipse.plugin; 11 | 12 | import org.eclipse.swt.events.MouseEvent; 13 | import org.eclipse.swt.events.MouseListener; 14 | 15 | public class CustomMouseListener implements MouseListener { 16 | 17 | @Override 18 | public void mouseDown(MouseEvent e) { 19 | // Logger.debug("CustomMouseListener.mouseDown"); 20 | 21 | Heartbeat heartbeat = WakaTime.getHeartbeat(null, false); 22 | WakaTime.processHeartbeat(heartbeat); 23 | } 24 | 25 | @Override 26 | public void mouseDoubleClick(MouseEvent e) { } 27 | 28 | @Override 29 | public void mouseUp(MouseEvent e) { } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/Debouncer.java: -------------------------------------------------------------------------------- 1 | package com.wakatime.eclipse.plugin; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import java.util.concurrent.Executors; 5 | import java.util.concurrent.Future; 6 | import java.util.concurrent.ScheduledExecutorService; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class Debouncer { 10 | private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); 11 | private final ConcurrentHashMap> delayedMap = new ConcurrentHashMap>(); 12 | 13 | public void debounce(final String key, final Runnable runnable, long delay, TimeUnit unit) { 14 | final Future prev = delayedMap.put(key, (Future) scheduler.schedule(new Runnable() { 15 | @Override 16 | public void run() { 17 | try { 18 | runnable.run(); 19 | } finally { 20 | delayedMap.remove(key); 21 | } 22 | } 23 | }, delay, unit)); 24 | if (prev != null) { 25 | prev.cancel(true); 26 | } 27 | } 28 | 29 | public void shutdown() { 30 | scheduler.shutdownNow(); 31 | } 32 | } -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/MenuHandler.java: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | File: MenuHandler.java 3 | Description: Prompts user for api key if it does not exist. 4 | Maintainer: WakaTime 5 | License: BSD, see LICENSE for more details. 6 | Website: https://wakatime.com/ 7 | ===========================================================*/ 8 | 9 | 10 | package com.wakatime.eclipse.plugin; 11 | 12 | import org.eclipse.core.commands.AbstractHandler; 13 | import org.eclipse.core.commands.ExecutionEvent; 14 | import org.eclipse.core.commands.ExecutionException; 15 | import org.eclipse.ui.IWorkbenchWindow; 16 | import org.eclipse.ui.handlers.HandlerUtil; 17 | 18 | public class MenuHandler extends AbstractHandler { 19 | /** 20 | * The constructor. 21 | */ 22 | public MenuHandler() { 23 | } 24 | 25 | /** 26 | * the command has been executed, so extract extract the needed information 27 | * from the application context. 28 | */ 29 | public Object execute(ExecutionEvent event) throws ExecutionException { 30 | IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindowChecked(event); 31 | WakaTime.promptForApiKey(window); 32 | return null; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eclipse-wakatime 2 | 3 | Metrics, insights, and time tracking automatically generated from your programming activity. 4 | 5 | 6 | ## Installation 7 | 8 | 1. Click `Help → Eclipse Marketplace…` then search for `WakaTime`. 9 | 10 | ![Eclipse marketplace menu](https://wakatime.com/static/img/plugins/installing/eclipse.png) 11 | 12 | 2. Click `Install`. 13 | 14 | 3. Navigate to `File → WakaTime → API Key` and enter your [api key](https://wakatime.com/settings#apikey). 15 | 16 | 4. Use Eclipse and your coding activity will be displayed on your [WakaTime dashboard](https://wakatime.com). 17 | 18 | 19 | ## Screen Shots 20 | 21 | ![Project Overview](https://wakatime.com/static/img/ScreenShots/Screen-Shot-2016-03-21.png) 22 | 23 | 24 | ## Installation for Old Versions of Eclipse 25 | 26 | 1. Click `Help → Install New Software...` then enter this url: 27 | 28 | `https://raw.githubusercontent.com/wakatime/eclipse-wakatime/master/update-site/` 29 | 30 | 2. Make sure `WakaTime` is checked, then click `Next` to install the plugin. 31 | 32 | 3. Click `File → WakaTime → API Key`, enter your [api key](https://wakatime.com/settings#apikey), then click `OK`. 33 | 34 | 4. Use Eclipse and your coding activity will be displayed on your [WakaTime dashboard](https://wakatime.com). 35 | 36 | Note: WakaTime on OS X depends on Xcode being installed to work correctly. 37 | 38 | 39 | ## Troubleshooting 40 | 41 | For troubleshooting information, see [wakatime/wakatime-cli#troubleshooting](https://github.com/wakatime/wakatime-cli#troubleshooting). 42 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/Logger.java: -------------------------------------------------------------------------------- 1 | package com.wakatime.eclipse.plugin; 2 | 3 | import org.eclipse.core.runtime.Status; 4 | 5 | public class Logger { 6 | 7 | public static void info(String msg) { 8 | logMessage(msg, Status.INFO, null); 9 | } 10 | 11 | public static void debug(String msg) { 12 | if (WakaTime.getDefault().DEBUG) 13 | logMessage(msg, Status.INFO, null); 14 | } 15 | 16 | public static void debug(String msg, Exception e) { 17 | if (WakaTime.getDefault().DEBUG) 18 | logMessage(msg, Status.ERROR, e); 19 | } 20 | 21 | public static void debug(Exception e) { 22 | if (WakaTime.getDefault().DEBUG) 23 | logMessage("Debug", Status.ERROR, e); 24 | } 25 | 26 | public static void error(String msg) { 27 | logMessage(msg, Status.ERROR, null); 28 | } 29 | 30 | public static void error(String msg, Exception e) { 31 | logMessage(msg, Status.ERROR, e); 32 | } 33 | 34 | public static void error(Exception e) { 35 | logMessage("Error", Status.ERROR, e); 36 | } 37 | 38 | public static void warn(String msg, Exception e) { 39 | logMessage(msg, Status.WARNING, e); 40 | } 41 | 42 | public static void warn(Exception e) { 43 | logMessage("Warning", Status.WARNING, e); 44 | } 45 | 46 | public static void logMessage(String msg, int level, Exception e) { 47 | if (WakaTime.getDefault().logInstance != null) 48 | WakaTime.getDefault().logInstance.log(new Status(level, WakaTime.PLUGIN_ID, Status.OK, msg, e)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2014 by the respective authors (see AUTHORS file). 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer 14 | in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * Neither the names of WakaTime, nor the names of its 18 | contributors may be used to endorse or promote products derived 19 | from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND 22 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 23 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 25 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 26 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 27 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 28 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 29 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 30 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 13 | 14 | 18 | 19 | 20 | 22 | 25 | 26 | 27 | 29 | 34 | 35 | 36 | 38 | 40 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.feature/feature.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | Metrics, insights, and time tracking automatically generated from your programming activity. 10 | 11 | 12 | 13 | WakaTime 14 | 15 | 16 | 17 | BSD 3-Clause License 18 | 19 | Copyright (c) 2015 by the respective authors (see AUTHORS file). 20 | All rights reserved. 21 | 22 | Redistribution and use in source and binary forms, with or without 23 | modification, are permitted provided that the following conditions are met: 24 | 25 | * Redistributions of source code must retain the above copyright 26 | notice, this list of conditions and the following disclaimer. 27 | 28 | * Redistributions in binary form must reproduce the above copyright 29 | notice, this list of conditions and the following disclaimer 30 | in the documentation and/or other materials provided 31 | with the distribution. 32 | 33 | * Neither the names of WakaTime, nor the names of its 34 | contributors may be used to endorse or promote products derived 35 | from this software without specific prior written permission. 36 | 37 | THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND 38 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT 39 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 40 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 41 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 42 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 43 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 44 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 45 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 46 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 47 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 48 | 49 | 50 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/CustomEditorListener.java: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | File: CustomEditorListener.java 3 | Description: Automatic time tracking for Eclipse. 4 | Maintainer: WakaTime 5 | License: BSD, see LICENSE for more details. 6 | Website: https://wakatime.com/ 7 | ===========================================================*/ 8 | 9 | 10 | package com.wakatime.eclipse.plugin; 11 | 12 | import org.eclipse.jface.text.IDocument; 13 | import org.eclipse.jface.text.IDocumentListener; 14 | import org.eclipse.swt.custom.StyledText; 15 | import org.eclipse.swt.widgets.Control; 16 | import org.eclipse.ui.IEditorPart; 17 | import org.eclipse.ui.IEditorReference; 18 | import org.eclipse.ui.IPartListener2; 19 | import org.eclipse.ui.IWorkbenchPartReference; 20 | import org.eclipse.ui.texteditor.IDocumentProvider; 21 | import org.eclipse.ui.texteditor.ITextEditor; 22 | 23 | public class CustomEditorListener implements IPartListener2 { 24 | 25 | private static IDocument lastDocument = null; 26 | private static IDocumentListener documentListener = new CustomDocumentListener(); 27 | 28 | @Override 29 | public void partActivated(IWorkbenchPartReference partRef) { 30 | // Logger.debug("CustomEditorListener.partActivated"); 31 | 32 | IEditorPart activeEditor = partRef.getPage().getActiveEditor(); 33 | Heartbeat heartbeat = WakaTime.getHeartbeat(activeEditor, false); 34 | WakaTime.processHeartbeat(heartbeat); 35 | } 36 | 37 | @Override 38 | public void partOpened(IWorkbenchPartReference partRef) { 39 | // listen for caret movement in newly active editor 40 | IEditorPart editor = null; 41 | try { 42 | editor = ((IEditorReference) partRef).getEditor(false); 43 | } catch (java.lang.ClassCastException e) { 44 | // expected to fail sometimes, ignore it 45 | return; 46 | } catch (Exception e) { 47 | Logger.debug(e); 48 | return; 49 | } 50 | if (editor == null) return; 51 | 52 | try { 53 | ITextEditor textEditor = (ITextEditor) partRef; 54 | IDocumentProvider provider = textEditor.getDocumentProvider(); 55 | if (provider != null) { 56 | IDocument document = provider.getDocument(textEditor); 57 | if (document != null) { 58 | if (lastDocument != null) { 59 | lastDocument.removeDocumentListener(documentListener); 60 | } 61 | document.addDocumentListener(documentListener); 62 | lastDocument = document; 63 | } 64 | } 65 | } catch (java.lang.ClassCastException e) { 66 | // expected to fail sometimes, ignore it 67 | return; 68 | } catch (Exception e) { 69 | Logger.debug(e); 70 | return; 71 | } 72 | 73 | Control adapter = editor.getAdapter(Control.class); 74 | if (adapter == null) return; 75 | 76 | // listen for mouse clicks 77 | try { 78 | adapter.addMouseListener(new CustomMouseListener()); 79 | } catch (Exception e) { 80 | Logger.debug(e); 81 | } 82 | 83 | // listen for cursor movement and typing 84 | try { 85 | ((StyledText) adapter).addCaretListener(new CustomCaretListener()); 86 | } catch (Exception e) { 87 | Logger.debug(e); 88 | } 89 | } 90 | 91 | @Override 92 | public void partHidden(IWorkbenchPartReference partRef) { } 93 | 94 | @Override 95 | public void partVisible(IWorkbenchPartReference partRef) { } 96 | 97 | @Override 98 | public void partInputChanged(IWorkbenchPartReference partRef) { } 99 | 100 | @Override 101 | public void partBroughtToTop(IWorkbenchPartReference partRef) { } 102 | 103 | @Override 104 | public void partClosed(IWorkbenchPartReference partRef) { } 105 | 106 | @Override 107 | public void partDeactivated(IWorkbenchPartReference partRef) { } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/CustomExecutionListener.java: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | File: CustomExecutionListener.java 3 | Description: Automatic time tracking for Eclipse. 4 | Maintainer: WakaTime 5 | License: BSD, see LICENSE for more details. 6 | Website: https://wakatime.com/ 7 | ===========================================================*/ 8 | 9 | 10 | package com.wakatime.eclipse.plugin; 11 | 12 | import java.util.Arrays; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | import org.eclipse.core.commands.ExecutionEvent; 16 | import org.eclipse.core.commands.ExecutionException; 17 | import org.eclipse.core.commands.IExecutionListener; 18 | import org.eclipse.core.commands.NotHandledException; 19 | import org.eclipse.ui.IWorkbenchCommandConstants; 20 | 21 | public class CustomExecutionListener implements IExecutionListener { 22 | 23 | private final String[] postExecCommands = new String[]{ 24 | IWorkbenchCommandConstants.FILE_SAVE, 25 | IWorkbenchCommandConstants.EDIT_COPY, 26 | IWorkbenchCommandConstants.EDIT_CUT, 27 | IWorkbenchCommandConstants.EDIT_PASTE, 28 | IWorkbenchCommandConstants.EDIT_UNDO, 29 | IWorkbenchCommandConstants.EDIT_REDO, 30 | IWorkbenchCommandConstants.PROJECT_BUILD_ALL, 31 | IWorkbenchCommandConstants.PROJECT_BUILD_PROJECT, 32 | "org.jkiss.dbeaver.core.object.open", 33 | "org.eclipse.ui.edit.text.openLocalFile", 34 | }; 35 | 36 | final String[] buildCommands = new String[]{ 37 | IWorkbenchCommandConstants.PROJECT_BUILD_ALL, 38 | IWorkbenchCommandConstants.PROJECT_BUILD_PROJECT, 39 | }; 40 | 41 | @Override 42 | public void preExecute(String commandId, ExecutionEvent event) { 43 | // Logger.debug("CustomExecutionListener.preExecute: " + commandId); 44 | if (!Arrays.asList(buildCommands).contains(commandId)) return; 45 | 46 | final Heartbeat heartbeat = WakaTime.getHeartbeat(null, false, true); 47 | WakaTime.getDefault().debouncer.debounce("manual-build", new Runnable() { 48 | @Override public void run() { 49 | // TODO: set a periodic timer to send heartbeats for long builds when user left for coffee 50 | WakaTime.getDefault().isBuilding = true; 51 | WakaTime.processHeartbeat(heartbeat); 52 | WakaTime.startWatchingBuild(); 53 | } 54 | }, WakaTime.BUILD_THRESHOLD, TimeUnit.SECONDS); 55 | } 56 | 57 | @Override 58 | public void postExecuteSuccess(String commandId, Object returnValue) { 59 | // Logger.debug("CustomExecutionListener.postExecuteSuccess: " + commandId); 60 | if (!Arrays.asList(postExecCommands).contains(commandId)) return; 61 | 62 | final boolean isWrite = commandId.equals(IWorkbenchCommandConstants.FILE_SAVE); 63 | 64 | if (Arrays.asList(buildCommands).contains(commandId)) { 65 | final Heartbeat heartbeat = WakaTime.getHeartbeat(null, isWrite, false); 66 | WakaTime.getDefault().debouncer.debounce("manual-build", new Runnable() { 67 | @Override public void run() { 68 | WakaTime.getDefault().isBuilding = false; 69 | WakaTime.processHeartbeat(heartbeat); 70 | WakaTime.stopWatchingBuild(); 71 | } 72 | }, 1, TimeUnit.MILLISECONDS); 73 | return; 74 | } 75 | 76 | Heartbeat heartbeat = WakaTime.getHeartbeat(null, isWrite); 77 | WakaTime.processHeartbeat(heartbeat); 78 | } 79 | 80 | @Override 81 | public void notHandled(String commandId, NotHandledException exception) { } 82 | 83 | @Override 84 | public void postExecuteFailure(String commandId, ExecutionException exception) { 85 | // Logger.debug("CustomExecutionListener.postExecuteFailure: " + commandId); 86 | if (!Arrays.asList(buildCommands).contains(commandId)) return; 87 | 88 | final Heartbeat heartbeat = WakaTime.getHeartbeat(null, false, false); 89 | WakaTime.getDefault().debouncer.debounce("manual-build", new Runnable() { 90 | @Override public void run() { 91 | WakaTime.getDefault().isBuilding = false; 92 | WakaTime.processHeartbeat(heartbeat); 93 | WakaTime.stopWatchingBuild(); 94 | } 95 | }, 1, TimeUnit.MILLISECONDS); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/ConfigFile.java: -------------------------------------------------------------------------------- 1 | package com.wakatime.eclipse.plugin; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileNotFoundException; 6 | import java.io.FileReader; 7 | import java.io.IOException; 8 | import java.io.PrintWriter; 9 | import java.io.UnsupportedEncodingException; 10 | 11 | public class ConfigFile { 12 | private static final String fileName = ".wakatime.cfg"; 13 | private static final String internalFileName = ".wakatime-internal.cfg"; 14 | private static String cachedConfigFile = null; 15 | private static String _api_key = ""; 16 | 17 | private static String getConfigFilePath(boolean internal) { 18 | if (ConfigFile.cachedConfigFile == null) { 19 | if (System.getenv("WAKATIME_HOME") != null && !System.getenv("WAKATIME_HOME").trim().isEmpty()) { 20 | File folder = new File(System.getenv("WAKATIME_HOME")); 21 | if (folder.exists()) { 22 | ConfigFile.cachedConfigFile = folder.getAbsolutePath(); 23 | Logger.debug("Using $WAKATIME_HOME for config folder: " + ConfigFile.cachedConfigFile); 24 | return new File(ConfigFile.cachedConfigFile, internal ? internalFileName : fileName).getAbsolutePath(); 25 | } 26 | } 27 | ConfigFile.cachedConfigFile = new File(System.getProperty("user.home")).getAbsolutePath(); 28 | Logger.debug("Using $HOME for config folder: " + ConfigFile.cachedConfigFile); 29 | } 30 | return new File(ConfigFile.cachedConfigFile, internal ? internalFileName : fileName).getAbsolutePath(); 31 | } 32 | 33 | public static String get(String section, String key, boolean internal) { 34 | String file = ConfigFile.getConfigFilePath(internal); 35 | String val = null; 36 | try { 37 | BufferedReader br = new BufferedReader(new FileReader(file)); 38 | String currentSection = ""; 39 | try { 40 | String line = br.readLine(); 41 | while (line != null) { 42 | if (line.trim().startsWith("[") && line.trim().endsWith("]")) { 43 | currentSection = line.trim().substring(1, line.trim().length() - 1).toLowerCase(); 44 | } else { 45 | if (section.toLowerCase().equals(currentSection)) { 46 | String[] parts = line.split("="); 47 | if (parts.length == 2 && parts[0].trim().equals(key)) { 48 | val = parts[1].trim(); 49 | br.close(); 50 | return removeNulls(val); 51 | } 52 | } 53 | } 54 | line = br.readLine(); 55 | } 56 | } catch (Exception e) { 57 | e.printStackTrace(); 58 | } finally { 59 | try { 60 | br.close(); 61 | } catch (IOException e) { 62 | e.printStackTrace(); 63 | } 64 | } 65 | } catch (FileNotFoundException e1) { /* ignored */ } 66 | return removeNulls(val); 67 | } 68 | 69 | public static void set(String section, String key, boolean internal, String val) { 70 | key = removeNulls(key); 71 | val = removeNulls(val); 72 | 73 | String file = ConfigFile.getConfigFilePath(internal); 74 | StringBuilder contents = new StringBuilder(); 75 | try { 76 | BufferedReader br = new BufferedReader(new FileReader(file)); 77 | try { 78 | String currentSection = ""; 79 | String line = br.readLine(); 80 | Boolean found = false; 81 | while (line != null) { 82 | line = removeNulls(line); 83 | if (line.trim().startsWith("[") && line.trim().endsWith("]")) { 84 | if (section.toLowerCase().equals(currentSection) && !found) { 85 | contents.append(key + " = " + val + "\n"); 86 | found = true; 87 | } 88 | currentSection = line.trim().substring(1, line.trim().length() - 1).toLowerCase(); 89 | contents.append(line + "\n"); 90 | } else { 91 | if (section.toLowerCase().equals(currentSection)) { 92 | String[] parts = line.split("="); 93 | String currentKey = parts[0].trim(); 94 | if (currentKey.equals(key)) { 95 | if (!found) { 96 | contents.append(key + " = " + val + "\n"); 97 | found = true; 98 | } 99 | } else { 100 | contents.append(line + "\n"); 101 | } 102 | } else { 103 | contents.append(line + "\n"); 104 | } 105 | } 106 | line = br.readLine(); 107 | } 108 | if (!found) { 109 | if (!section.toLowerCase().equals(currentSection)) { 110 | contents.append("[" + section.toLowerCase() + "]\n"); 111 | } 112 | contents.append(key + " = " + val + "\n"); 113 | } 114 | } catch (Exception e) { 115 | e.printStackTrace(); 116 | } finally { 117 | try { 118 | br.close(); 119 | } catch (IOException e) { 120 | e.printStackTrace(); 121 | } 122 | } 123 | } catch (FileNotFoundException e1) { 124 | 125 | // cannot read config file, so create it 126 | contents = new StringBuilder(); 127 | contents.append("[" + section.toLowerCase() + "]\n"); 128 | contents.append(key + " = " + val + "\n"); 129 | } 130 | 131 | PrintWriter writer = null; 132 | try { 133 | writer = new PrintWriter(file, "UTF-8"); 134 | } catch (FileNotFoundException e) { 135 | e.printStackTrace(); 136 | } catch (UnsupportedEncodingException e) { 137 | e.printStackTrace(); 138 | } 139 | if (writer != null) { 140 | writer.print(contents.toString()); 141 | writer.close(); 142 | } 143 | } 144 | 145 | public static String getApiKey() { 146 | if (!ConfigFile._api_key.equals("")) { 147 | return ConfigFile._api_key; 148 | } 149 | 150 | String apiKey = get("settings", "api_key", false); 151 | if (apiKey == null) apiKey = ""; 152 | 153 | ConfigFile._api_key = apiKey; 154 | return apiKey; 155 | } 156 | 157 | public static void setApiKey(String apiKey) { 158 | set("settings", "api_key", false, apiKey); 159 | ConfigFile._api_key = apiKey; 160 | } 161 | 162 | private static String removeNulls(String s) { 163 | if (s == null) return null; 164 | return s.replace("\0", ""); 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/Heartbeat.java: -------------------------------------------------------------------------------- 1 | package com.wakatime.eclipse.plugin; 2 | 3 | import java.math.BigDecimal; 4 | import java.net.URI; 5 | import java.util.ArrayList; 6 | 7 | import org.eclipse.core.resources.IProject; 8 | import org.eclipse.core.resources.IResource; 9 | import org.eclipse.core.resources.IWorkspace; 10 | import org.eclipse.core.resources.IWorkspaceRoot; 11 | import org.eclipse.core.resources.ResourcesPlugin; 12 | import org.eclipse.jface.text.BadLocationException; 13 | import org.eclipse.jface.text.IDocument; 14 | import org.eclipse.jface.text.ITextSelection; 15 | import org.eclipse.jface.viewers.ISelectionProvider; 16 | import org.eclipse.ui.IEditorInput; 17 | import org.eclipse.ui.IEditorPart; 18 | import org.eclipse.ui.IWorkbenchPartSite; 19 | import org.eclipse.ui.part.FileEditorInput; 20 | import org.eclipse.ui.texteditor.IDocumentProvider; 21 | import org.eclipse.ui.texteditor.ITextEditor; 22 | 23 | public class Heartbeat { 24 | public String entity; 25 | public String entityType; 26 | public BigDecimal timestamp; 27 | public boolean isWrite; 28 | public IProject project; 29 | public Integer lineCount; 30 | public Integer lineNumber; 31 | public Integer cursorPosition; 32 | public String alternateLanguage; 33 | public boolean isBuilding; 34 | public boolean isUnsavedFile; 35 | public boolean isUnsavedFile2; 36 | 37 | public Heartbeat(String entity, boolean isWrite, IEditorPart activeEditor, boolean isDatabase, Boolean isBuilding) { 38 | super(); 39 | 40 | this.entity = entity; 41 | this.entityType = "file"; 42 | this.timestamp = WakaTime.getCurrentTimestamp(); 43 | this.isWrite = isWrite; 44 | this.setProject(activeEditor); 45 | if (isBuilding != null) { 46 | this.isBuilding = isBuilding; 47 | } else { 48 | this.isBuilding = WakaTime.getDefault().isBuilding || WakaTime.getDefault().isAutoBuilding; 49 | } 50 | this.isUnsavedFile = isDatabase; 51 | if (!isDatabase) { 52 | this.fixFilePath(); 53 | this.setFileMetadata(activeEditor); 54 | } 55 | } 56 | 57 | public void setFileMetadata(IEditorPart activeEditor) { 58 | ITextEditor editor = (ITextEditor) activeEditor.getAdapter(ITextEditor.class); 59 | if (editor == null) return; 60 | 61 | IDocumentProvider docProvider = editor.getDocumentProvider(); 62 | if (docProvider == null) return; 63 | 64 | IEditorInput input = activeEditor.getEditorInput(); 65 | if (input == null) return; 66 | 67 | IDocument document = docProvider.getDocument(input); 68 | if (document == null) return; 69 | 70 | this.lineCount = document.getNumberOfLines(); 71 | 72 | IWorkbenchPartSite site = activeEditor.getSite(); 73 | if (site == null) return; 74 | 75 | ISelectionProvider selectionProvider = site.getSelectionProvider(); 76 | if (selectionProvider == null) return; 77 | 78 | ITextSelection selection = (ITextSelection) selectionProvider.getSelection(); 79 | if (selection == null) return; 80 | 81 | int line = selection.getStartLine(); 82 | if (line < 0) return; 83 | 84 | int offset = selection.getOffset(); 85 | if (offset < 0) return; 86 | 87 | try { 88 | int cursor = offset - document.getLineOffset(line); 89 | if (cursor < 0) return; 90 | 91 | this.cursorPosition = cursor + 1; 92 | this.lineNumber = line + 1; 93 | 94 | } catch (BadLocationException e) { 95 | Logger.debug(e); 96 | } 97 | } 98 | 99 | public boolean canSend() { 100 | if (this.isWrite) return true; 101 | 102 | if (enoughTimePassed()) return true; 103 | 104 | if (this.isBuilding != WakaTime.getDefault().lastIsBuilding) return true; 105 | 106 | return !this.entity.equals(WakaTime.getDefault().lastFile); 107 | } 108 | 109 | public boolean enoughTimePassed() { 110 | return WakaTime.getDefault().lastTime.add(WakaTime.FREQUENCY).compareTo(this.timestamp) < 0; 111 | } 112 | 113 | public String[] toCliCommands() { 114 | ArrayList cmds = new ArrayList(); 115 | cmds.add(Dependencies.getCLILocation()); 116 | cmds.add("--entity"); 117 | cmds.add(this.entity); 118 | cmds.add("--time"); 119 | cmds.add(this.timestamp.toPlainString()); 120 | cmds.add("--plugin"); 121 | cmds.add(WakaTime.getDefault().IDE_NAME + "/" + WakaTime.getDefault().ECLIPSE_VERSION + " eclipse-wakatime/" + WakaTime.getDefault().VERSION); 122 | if (this.project != null && this.project.getName() != null) { 123 | cmds.add("--project"); 124 | cmds.add(this.project.getName()); 125 | } 126 | if (this.isWrite) 127 | cmds.add("--write"); 128 | if (this.isUnsavedFile) { 129 | cmds.add("--is-unsaved-entity"); 130 | } 131 | String projectFolder = this.getProjectFolder(); 132 | if (projectFolder != null) { 133 | cmds.add("--project-folder"); 134 | cmds.add(projectFolder); 135 | } 136 | if (this.isBuilding) { 137 | cmds.add("--category"); 138 | cmds.add("building"); 139 | } 140 | if (this.alternateLanguage != null) { 141 | cmds.add("--alternate-language"); 142 | cmds.add(this.alternateLanguage); 143 | } 144 | if (this.cursorPosition != null) { 145 | cmds.add("--cursorpos"); 146 | cmds.add(Integer.toString(this.cursorPosition)); 147 | } 148 | if (this.lineNumber != null) { 149 | cmds.add("--lineno"); 150 | cmds.add(Integer.toString(this.lineNumber)); 151 | } 152 | if (this.lineCount != null) { 153 | cmds.add("--lines-in-file"); 154 | cmds.add(Integer.toString(this.lineCount)); 155 | } 156 | if (WakaTime.getDefault().DEBUG) { 157 | Logger.debug(cmds.toString()); 158 | cmds.add("--verbose"); 159 | } 160 | return cmds.toArray(new String[cmds.size()]); 161 | } 162 | 163 | private void setProject(IEditorPart activeEditor) { 164 | if (activeEditor == null) { 165 | setDefaultProject(); 166 | return; 167 | } 168 | 169 | IEditorInput input = activeEditor.getEditorInput(); 170 | if (input == null) { 171 | setDefaultProject(); 172 | return; 173 | } 174 | 175 | if (input instanceof FileEditorInput) { 176 | IProject project = ((FileEditorInput)input).getFile().getProject(); 177 | if (project != null && project.getName() != null) { 178 | this.project = project; 179 | WakaTime.getDefault().lastProject = this.project; 180 | return; 181 | } 182 | } 183 | 184 | IProject project = input.getAdapter(IProject.class); 185 | if (project != null && project.getName() != null) { 186 | this.project = project; 187 | WakaTime.getDefault().lastProject = this.project; 188 | return; 189 | } 190 | 191 | IResource resource = input.getAdapter(IResource.class); 192 | if (resource == null) { 193 | setDefaultProject(); 194 | return; 195 | } 196 | 197 | IProject resourceProject = resource.getProject(); 198 | if (resourceProject == null || resourceProject.getName() == null) { 199 | setDefaultProject(); 200 | return; 201 | } 202 | 203 | this.project = resourceProject; 204 | WakaTime.getDefault().lastProject = this.project; 205 | } 206 | 207 | private void setDefaultProject() { 208 | IProject lastProject = WakaTime.getDefault().lastProject; 209 | if (lastProject != null) { 210 | this.project = lastProject; 211 | return; 212 | } 213 | 214 | try { 215 | IWorkspace workspace = ResourcesPlugin.getWorkspace(); 216 | IWorkspaceRoot root = workspace.getRoot(); 217 | IProject[] projects = root.getProjects(); 218 | for (IProject project : projects) { 219 | if (project != null && project.isOpen()) { 220 | this.project = project; 221 | return; 222 | } 223 | } 224 | } catch (Exception e) { 225 | Logger.debug(e); 226 | } 227 | } 228 | 229 | private String getProjectFolder() { 230 | if (this.project == null) return null; 231 | 232 | URI root = this.project.getLocationURI(); 233 | if (root == null) return null; 234 | 235 | return root.getPath(); 236 | } 237 | 238 | private void fixFilePath() { 239 | this.entity = this.entity.replaceFirst("^[\\\\/]([A-Z]:[\\\\/])", "$1"); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/WakaTime.java: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | File: WakaTime.java 3 | Description: Automatic time tracking for Eclipse. 4 | Maintainer: WakaTime 5 | License: BSD, see LICENSE for more details. 6 | Website: https://wakatime.com/ 7 | ===========================================================*/ 8 | 9 | 10 | package com.wakatime.eclipse.plugin; 11 | 12 | import java.io.BufferedReader; 13 | import java.io.File; 14 | import java.io.InputStreamReader; 15 | import java.math.BigDecimal; 16 | import java.net.URI; 17 | import java.util.concurrent.Executors; 18 | import java.util.concurrent.ScheduledExecutorService; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | import org.eclipse.core.commands.IExecutionListener; 22 | import org.eclipse.core.resources.IProject; 23 | import org.eclipse.core.resources.ResourcesPlugin; 24 | import org.eclipse.core.runtime.ILog; 25 | import org.eclipse.core.runtime.IStatus; 26 | import org.eclipse.core.runtime.Platform; 27 | import org.eclipse.core.runtime.jobs.IJobChangeEvent; 28 | import org.eclipse.core.runtime.jobs.Job; 29 | import org.eclipse.core.runtime.jobs.JobChangeAdapter; 30 | import org.eclipse.jface.dialogs.InputDialog; 31 | import org.eclipse.jface.resource.ImageDescriptor; 32 | import org.eclipse.ui.IEditorInput; 33 | import org.eclipse.ui.IEditorPart; 34 | import org.eclipse.ui.IFileEditorInput; 35 | import org.eclipse.ui.IPartService; 36 | import org.eclipse.ui.IPathEditorInput; 37 | import org.eclipse.ui.IStartup; 38 | import org.eclipse.ui.IURIEditorInput; 39 | import org.eclipse.ui.IWorkbench; 40 | import org.eclipse.ui.IWorkbenchPage; 41 | import org.eclipse.ui.IWorkbenchPart; 42 | import org.eclipse.ui.IWorkbenchPartSite; 43 | import org.eclipse.ui.IWorkbenchWindow; 44 | import org.eclipse.ui.PlatformUI; 45 | import org.eclipse.ui.commands.ICommandService; 46 | import org.eclipse.ui.plugin.AbstractUIPlugin; 47 | import org.osgi.framework.BundleContext; 48 | 49 | /** 50 | * The activator class controls the plug-in life cycle 51 | */ 52 | public class WakaTime extends AbstractUIPlugin implements IStartup { 53 | 54 | // The plug-in ID 55 | public static final String PLUGIN_ID = "com.wakatime.eclipse.plugin"; 56 | 57 | // The shared instance 58 | public String IDE_NAME; 59 | public String ECLIPSE_VERSION; 60 | public boolean isBuilding = false; 61 | public boolean isAutoBuilding = false; 62 | public static WakaTime plugin; 63 | public ILog logInstance; 64 | public boolean DEBUG = false; 65 | public static Boolean READY = false; 66 | 67 | // Listeners 68 | private static CustomEditorListener editorListener; 69 | private static IExecutionListener executionListener; 70 | 71 | // Schedulers 72 | public Debouncer debouncer; 73 | public ScheduledExecutorService buildScheduler; 74 | public ScheduledExecutorService autoBuildScheduler; 75 | 76 | // Constants 77 | public static final BigDecimal FREQUENCY = new BigDecimal(2 * 60); 78 | public static final int BUILD_THRESHOLD = 3; 79 | public final String VERSION = Platform.getBundle(PLUGIN_ID).getVersion().toString(); 80 | 81 | public String lastFile; 82 | public BigDecimal lastTime = new BigDecimal(0); 83 | public IProject lastProject; 84 | public boolean lastIsBuilding = false; 85 | 86 | /** 87 | * The constructor 88 | */ 89 | public WakaTime() { 90 | } 91 | 92 | /* 93 | * (non-Javadoc) 94 | * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) 95 | */ 96 | public void start(BundleContext context) throws Exception { 97 | logInstance = getLog(); 98 | 99 | super.start(context); 100 | plugin = this; 101 | 102 | // discover app name and version 103 | try { 104 | ECLIPSE_VERSION = Platform.getBundle("org.eclipse.platform").getVersion().toString(); 105 | IDE_NAME = "eclipse"; 106 | } catch (Exception e) { 107 | try { 108 | ECLIPSE_VERSION = Platform.getBundle("org.jkiss.dbeaver.core").getVersion().toString(); 109 | IDE_NAME = "dbeaver"; 110 | } catch (Exception e2) { 111 | try { 112 | ECLIPSE_VERSION = Platform.getProduct().getDefiningBundle().getVersion().toString(); 113 | IDE_NAME = Platform.getProduct().getName(); 114 | } catch (Exception e3) { 115 | ECLIPSE_VERSION = "unknown"; 116 | IDE_NAME = "eclipse"; 117 | } 118 | } 119 | } 120 | Logger.debug("Detected " + IDE_NAME + " version: " + ECLIPSE_VERSION); 121 | 122 | editorListener = new CustomEditorListener(); 123 | debouncer = new Debouncer(); 124 | } 125 | 126 | @Override 127 | public void earlyStartup() { 128 | final IWorkbench workbench = PlatformUI.getWorkbench(); 129 | 130 | workbench.getDisplay().asyncExec(new Runnable() { 131 | public void run() { 132 | try { 133 | IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); 134 | if (window == null) return; 135 | 136 | String debug = ConfigFile.get("settings", "debug", false); 137 | WakaTime.getDefault().DEBUG = debug != null && debug.trim().equals("true"); 138 | Logger.debug("Initializing WakaTime plugin (https://wakatime.com) v"+VERSION); 139 | 140 | // listen for file saved events 141 | ICommandService commandService = workbench.getService(ICommandService.class); 142 | executionListener = new CustomExecutionListener(); 143 | commandService.addExecutionListener(executionListener); 144 | 145 | // prompt for apiKey if not set 146 | String apiKey = ConfigFile.get("settings", "api_key", false); 147 | if (apiKey == "") promptForApiKey(window); 148 | 149 | checkCLI(); 150 | 151 | // log file if one is already opened on startup 152 | Heartbeat heartbeat = WakaTime.getHeartbeat(null, false); 153 | WakaTime.processHeartbeat(heartbeat); 154 | 155 | // listen for focused file change 156 | if (window.getPartService() != null) window.getPartService().addPartListener(editorListener); 157 | 158 | // listen for auto-builds 159 | WakaTime.setupAutoBuildWatcher(); 160 | 161 | Logger.debug("Finished initializing WakaTime plugin (https://wakatime.com) v"+VERSION); 162 | } catch (Exception e) { 163 | Logger.debug(e); 164 | } 165 | } 166 | }); 167 | } 168 | 169 | /* 170 | * (non-Javadoc) 171 | * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) 172 | */ 173 | public void stop(BundleContext context) throws Exception { 174 | plugin = null; 175 | super.stop(context); 176 | 177 | IWorkbench workbench = PlatformUI.getWorkbench(); 178 | 179 | if (workbench != null) { 180 | ICommandService commandService = workbench.getService(ICommandService.class); 181 | if (commandService != null) commandService.removeExecutionListener(executionListener); 182 | 183 | IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); 184 | if (window != null && window.getPartService() != null) window.getPartService().removePartListener(editorListener); 185 | } 186 | 187 | debouncer.shutdown(); 188 | } 189 | 190 | public static void promptForApiKey(IWorkbenchWindow window) { 191 | String apiKey = ConfigFile.get("settings", "api_key", false); 192 | InputDialog dialog = new InputDialog(window.getShell(), 193 | "WakaTime API Key", "Enter your api key from http://wakatime.com/api-key", apiKey, null); 194 | if (dialog.open() == IStatus.OK) { 195 | apiKey = dialog.getValue(); 196 | ConfigFile.set("settings", "api_key", false, apiKey); 197 | } 198 | } 199 | 200 | private void checkCLI() { 201 | if (!Dependencies.isCLIInstalled()) { 202 | Logger.debug("Downloading and installing wakatime-cli..."); 203 | Dependencies.installCLI(); 204 | WakaTime.READY = true; 205 | Logger.debug("Finished downloading and installing wakatime-cli."); 206 | } else if (Dependencies.isCLIOld()) { 207 | if (System.getenv("WAKATIME_CLI_LOCATION") != null && !System.getenv("WAKATIME_CLI_LOCATION").trim().isEmpty()) { 208 | File wakatimeCLI = new File(System.getenv("WAKATIME_CLI_LOCATION")); 209 | if (wakatimeCLI.exists()) { 210 | Logger.error("$WAKATIME_CLI_LOCATION is out of date, please update it."); 211 | } 212 | } else { 213 | Logger.debug("Upgrading wakatime-cli ..."); 214 | Dependencies.installCLI(); 215 | WakaTime.READY = true; 216 | Logger.debug("Finished upgrading wakatime-cli."); 217 | } 218 | } else { 219 | WakaTime.READY = true; 220 | Logger.debug("wakatime-cli is up to date."); 221 | } 222 | Dependencies.createSymlink(Dependencies.combinePaths(Dependencies.getResourcesLocation(), "wakatime-cli"), Dependencies.getCLILocation()); 223 | Logger.debug("wakatime-cli location: " + Dependencies.getCLILocation()); 224 | } 225 | 226 | private static void setupAutoBuildWatcher() { 227 | try { 228 | Job.getJobManager().addJobChangeListener(new JobChangeAdapter() { 229 | @Override 230 | public void aboutToRun(IJobChangeEvent event) { 231 | if (event.getJob().belongsTo(ResourcesPlugin.FAMILY_AUTO_BUILD)) { 232 | // Logger.debug("Auto-build about to run."); 233 | final Heartbeat heartbeat = WakaTime.getHeartbeat(null, false, true); 234 | WakaTime.getDefault().debouncer.debounce("auto-build", new Runnable() { 235 | @Override public void run() { 236 | // TODO: set a periodic timer to send heartbeats for long builds when user left for coffee 237 | WakaTime.getDefault().isAutoBuilding = true; 238 | WakaTime.processHeartbeat(heartbeat); 239 | WakaTime.startWatchingAutoBuild(); 240 | } 241 | }, WakaTime.BUILD_THRESHOLD, TimeUnit.SECONDS); 242 | } 243 | } 244 | 245 | @Override 246 | public void done(IJobChangeEvent event) { 247 | if (event.getJob().belongsTo(ResourcesPlugin.FAMILY_AUTO_BUILD)) { 248 | // Logger.debug("Auto-build completed."); 249 | final Heartbeat heartbeat = WakaTime.getHeartbeat(null, false, false); 250 | WakaTime.getDefault().debouncer.debounce("auto-build", new Runnable() { 251 | @Override public void run() { 252 | WakaTime.getDefault().isAutoBuilding = false; 253 | WakaTime.processHeartbeat(heartbeat); 254 | WakaTime.stopWatchingAutoBuild(); 255 | } 256 | }, 1, TimeUnit.MILLISECONDS); 257 | } 258 | } 259 | }); 260 | } catch (Exception e) { 261 | Logger.debug(e); 262 | } 263 | } 264 | 265 | public static void processHeartbeat(Heartbeat heartbeat) { 266 | if (heartbeat == null) return; 267 | if (!heartbeat.canSend()) return; 268 | 269 | sendHeartbeat(heartbeat); 270 | 271 | WakaTime.getDefault().lastFile = heartbeat.entity; 272 | WakaTime.getDefault().lastTime = heartbeat.timestamp; 273 | WakaTime.getDefault().lastIsBuilding = heartbeat.isBuilding; 274 | } 275 | 276 | private static void sendHeartbeat(Heartbeat heartbeat) { 277 | if (!WakaTime.READY) return; 278 | 279 | final String[] cmds = heartbeat.toCliCommands(); 280 | 281 | Logger.debug(cmds.toString()); 282 | 283 | Runnable r = new Runnable() { 284 | public void run() { 285 | try { 286 | Process proc = Runtime.getRuntime().exec(cmds); 287 | if (getDefault().DEBUG) { 288 | BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream())); 289 | BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream())); 290 | proc.waitFor(); 291 | String s; 292 | while ((s = stdInput.readLine()) != null) { 293 | if (!s.trim().equals("")) Logger.debug(s); 294 | } 295 | while ((s = stdError.readLine()) != null) { 296 | if (!s.trim().equals("")) Logger.debug(s); 297 | } 298 | Logger.debug("Command finished with return value: " + proc.exitValue()); 299 | } 300 | } catch (Exception e) { 301 | Logger.error(e); 302 | } 303 | } 304 | }; 305 | new Thread(r).start(); 306 | } 307 | 308 | private static IEditorPart getActiveEditor() { 309 | IWorkbench workbench = PlatformUI.getWorkbench(); 310 | IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); 311 | if (window == null) return null; 312 | 313 | final IPartService partService = window.getPartService(); 314 | if (partService == null) return null; 315 | 316 | final IWorkbenchPart part = partService.getActivePart(); 317 | if (part == null) return null; 318 | 319 | final IWorkbenchPartSite site = part.getSite(); 320 | if (site == null) return null; 321 | 322 | final IWorkbenchPage page = site.getPage(); 323 | if (page == null) return null; 324 | 325 | return page.getActiveEditor(); 326 | } 327 | 328 | public static Heartbeat getHeartbeat(IEditorPart activeEditor, boolean isWrite) { 329 | return WakaTime.getHeartbeat(activeEditor, isWrite, false); 330 | } 331 | 332 | public static Heartbeat getHeartbeat(IEditorPart activeEditor, boolean isWrite, Boolean isBuilding) { 333 | if (activeEditor == null) activeEditor = getActiveEditor(); 334 | if (activeEditor == null) return null; 335 | 336 | IEditorInput editorInput = activeEditor.getEditorInput(); 337 | if (editorInput == null) return null; 338 | 339 | try { 340 | if (editorInput instanceof IURIEditorInput) { 341 | final URI uri = ((IURIEditorInput) editorInput).getURI(); 342 | if (uri != null && uri.getPath() != null && !uri.getPath().trim().equals("")) { 343 | return new Heartbeat(uri.getPath(), isWrite, activeEditor, false, isBuilding); 344 | } 345 | } else if (editorInput instanceof IFileEditorInput) { 346 | final URI uri = ((IFileEditorInput) editorInput).getFile().getLocationURI(); 347 | if (uri != null && uri.getPath() != null && !uri.getPath().trim().equals("")) { 348 | return new Heartbeat(uri.getPath(), isWrite, activeEditor, false, isBuilding); 349 | } 350 | } else if (editorInput instanceof IPathEditorInput) { 351 | return new Heartbeat(((IPathEditorInput) editorInput).getPath().makeAbsolute().toString(), isWrite, activeEditor, false, isBuilding); 352 | } 353 | 354 | } catch(Exception e) { 355 | Logger.error(e); 356 | } 357 | 358 | try { 359 | Class C = editorInput.getClass(); 360 | while (C != null) { 361 | if (C.getName().equals("org.jkiss.dbeaver.ui.editors.DatabaseEditorInput")) { 362 | try { 363 | return new Heartbeat(C.getMethod("getDatabaseObject").invoke(editorInput).toString(), isWrite, activeEditor, true, isBuilding); 364 | } catch (Exception e) { 365 | Logger.error(e); 366 | } 367 | } 368 | C = C.getSuperclass(); 369 | } 370 | 371 | } catch(Exception e) { 372 | Logger.error(e); 373 | } 374 | 375 | return null; 376 | } 377 | 378 | public static BigDecimal getCurrentTimestamp() { 379 | return new BigDecimal(String.valueOf(System.currentTimeMillis() / 1000.0)); 380 | } 381 | 382 | public static void startWatchingBuild() { 383 | if (WakaTime.getDefault().buildScheduler != null) { 384 | try { 385 | WakaTime.getDefault().buildScheduler.shutdownNow(); 386 | } catch (Exception e) { 387 | Logger.debug(e); 388 | } 389 | } 390 | WakaTime.getDefault().buildScheduler = Executors.newSingleThreadScheduledExecutor(); 391 | WakaTime.getDefault().buildScheduler.scheduleWithFixedDelay(new Runnable() { 392 | @Override 393 | public void run() { 394 | Heartbeat heartbeat = WakaTime.getHeartbeat(null, false); 395 | WakaTime.processHeartbeat(heartbeat); 396 | if (!WakaTime.getDefault().isBuilding && WakaTime.getDefault().buildScheduler != null) WakaTime.getDefault().buildScheduler.shutdownNow(); 397 | } 398 | }, 90, 90, TimeUnit.SECONDS); 399 | } 400 | 401 | public static void startWatchingAutoBuild() { 402 | if (WakaTime.getDefault().autoBuildScheduler != null) { 403 | try { 404 | WakaTime.getDefault().autoBuildScheduler.shutdownNow(); 405 | } catch (Exception e) { 406 | Logger.debug(e); 407 | } 408 | } 409 | WakaTime.getDefault().autoBuildScheduler = Executors.newSingleThreadScheduledExecutor(); 410 | WakaTime.getDefault().autoBuildScheduler.scheduleWithFixedDelay(new Runnable() { 411 | @Override 412 | public void run() { 413 | Heartbeat heartbeat = WakaTime.getHeartbeat(null, false); 414 | WakaTime.processHeartbeat(heartbeat); 415 | if (!WakaTime.getDefault().isAutoBuilding && WakaTime.getDefault().autoBuildScheduler != null) WakaTime.getDefault().autoBuildScheduler.shutdownNow(); 416 | } 417 | }, 90, 90, TimeUnit.SECONDS); 418 | } 419 | 420 | public static void stopWatchingBuild() { 421 | try { 422 | if (WakaTime.getDefault().buildScheduler != null) WakaTime.getDefault().buildScheduler.shutdownNow(); 423 | } catch (Exception e) { 424 | Logger.debug(e); 425 | } 426 | } 427 | 428 | public static void stopWatchingAutoBuild() { 429 | try { 430 | if (WakaTime.getDefault().autoBuildScheduler != null) WakaTime.getDefault().autoBuildScheduler.shutdownNow(); 431 | } catch (Exception e) { 432 | Logger.debug(e); 433 | } 434 | } 435 | 436 | /** 437 | * Returns the shared instance 438 | * 439 | * @return the shared instance 440 | */ 441 | public static WakaTime getDefault() { 442 | return plugin; 443 | } 444 | 445 | /** 446 | * Returns an image descriptor for the image file at the given 447 | * plug-in relative path 448 | * 449 | * @param path the path 450 | * @return the image descriptor 451 | */ 452 | public static ImageDescriptor getImageDescriptor(String path) { 453 | return imageDescriptorFromPlugin(PLUGIN_ID, path); 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /com.wakatime.eclipse.plugin/src/com/wakatime/eclipse/plugin/Dependencies.java: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | File: Dependencies.java 3 | Description: Manages plugin dependencies. 4 | Maintainer: WakaTime 5 | License: BSD, see LICENSE for more details. 6 | Website: https://wakatime.com/ 7 | ===========================================================*/ 8 | 9 | package com.wakatime.eclipse.plugin; 10 | 11 | import javax.net.ssl.HttpsURLConnection; 12 | import javax.net.ssl.SSLContext; 13 | import javax.net.ssl.TrustManager; 14 | import java.io.BufferedReader; 15 | import java.io.File; 16 | import java.io.FileInputStream; 17 | import java.io.FileOutputStream; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.io.InputStreamReader; 21 | import java.net.Authenticator; 22 | import java.net.MalformedURLException; 23 | import java.net.URI; 24 | import java.net.URISyntaxException; 25 | import java.net.URL; 26 | import java.net.UnknownHostException; 27 | import java.net.PasswordAuthentication; 28 | import java.nio.channels.Channels; 29 | import java.nio.channels.ReadableByteChannel; 30 | import java.nio.file.Files; 31 | import java.nio.file.StandardCopyOption; 32 | import java.security.KeyManagementException; 33 | import java.security.NoSuchAlgorithmException; 34 | import java.util.ArrayList; 35 | import java.util.Arrays; 36 | import java.util.regex.Matcher; 37 | import java.util.regex.Pattern; 38 | import java.util.zip.ZipEntry; 39 | import java.util.zip.ZipInputStream; 40 | 41 | 42 | class Response { 43 | public int statusCode; 44 | public String body; 45 | public String lastModified; 46 | 47 | public Response(int statusCode, String body, String lastModified) { 48 | this.statusCode = statusCode; 49 | this.body = body; 50 | this.lastModified = lastModified; 51 | } 52 | } 53 | 54 | public class Dependencies { 55 | private static String resourcesLocation = null; 56 | private static String cliVersion = null; 57 | private static Boolean alpha = null; 58 | private static String originalProxyHost = null; 59 | private static String originalProxyPort = null; 60 | 61 | public static String getResourcesLocation() { 62 | if (Dependencies.resourcesLocation != null) return Dependencies.resourcesLocation; 63 | 64 | if (System.getenv("WAKATIME_HOME") != null && !System.getenv("WAKATIME_HOME").trim().isEmpty()) { 65 | File resourcesFolder = new File(System.getenv("WAKATIME_HOME")); 66 | if (resourcesFolder.exists()) { 67 | Dependencies.resourcesLocation = resourcesFolder.getAbsolutePath(); 68 | Logger.debug("Using $WAKATIME_HOME for resources folder: " + Dependencies.resourcesLocation); 69 | return Dependencies.resourcesLocation; 70 | } 71 | } 72 | 73 | if (isWindows()) { 74 | File windowsHome = new File(System.getenv("USERPROFILE")); 75 | File resourcesFolder = new File(windowsHome, ".wakatime"); 76 | Dependencies.resourcesLocation = resourcesFolder.getAbsolutePath(); 77 | return Dependencies.resourcesLocation; 78 | } 79 | 80 | File userHomeDir = new File(System.getProperty("user.home")); 81 | File resourcesFolder = new File(userHomeDir, ".wakatime"); 82 | Dependencies.resourcesLocation = resourcesFolder.getAbsolutePath(); 83 | return Dependencies.resourcesLocation; 84 | } 85 | 86 | public static boolean isCLIInstalled() { 87 | File cli = new File(Dependencies.getCLILocation()); 88 | return cli.exists(); 89 | } 90 | 91 | public static boolean isCLIOld() { 92 | if (!Dependencies.isCLIInstalled()) { 93 | return false; 94 | } 95 | ArrayList cmds = new ArrayList(); 96 | cmds.add(Dependencies.getCLILocation()); 97 | cmds.add("--version"); 98 | try { 99 | Process p = Runtime.getRuntime().exec(cmds.toArray(new String[cmds.size()])); 100 | BufferedReader stdInput = new BufferedReader(new 101 | InputStreamReader(p.getInputStream())); 102 | BufferedReader stdError = new BufferedReader(new 103 | InputStreamReader(p.getErrorStream())); 104 | p.waitFor(); 105 | String output = ""; 106 | String s; 107 | while ((s = stdInput.readLine()) != null) { 108 | output += s; 109 | } 110 | while ((s = stdError.readLine()) != null) { 111 | output += s; 112 | } 113 | Logger.debug("wakatime-cli local version output: \"" + output + "\""); 114 | Logger.debug("wakatime-cli local version exit code: " + p.exitValue()); 115 | 116 | if (p.exitValue() == 0) { 117 | String cliVersion = latestCliVersion(); 118 | Logger.debug("Latest wakatime-cli version: " + cliVersion); 119 | if (output.trim().equals(cliVersion)) return false; 120 | } 121 | } catch (Exception e) { 122 | Logger.warn(e); 123 | } 124 | return true; 125 | } 126 | 127 | public static String latestCliVersion() { 128 | if (cliVersion != null) return cliVersion; 129 | String url = Dependencies.githubReleasesApiUrl(); 130 | try { 131 | Response resp = getUrlAsString(url, ConfigFile.get("internal", "cli_version_last_modified", true)); 132 | if (resp == null) { 133 | cliVersion = ConfigFile.get("internal", "cli_version", true).trim(); 134 | Logger.debug("Using cached wakatime-cli version from config: " + cliVersion); 135 | return cliVersion; 136 | } 137 | Pattern p = Pattern.compile(".*\"tag_name\":\\s*\"([^\"]+)\",.*"); 138 | Matcher m = p.matcher(resp.body); 139 | if (m.find()) { 140 | cliVersion = m.group(1); 141 | if (resp.lastModified != null) { 142 | ConfigFile.set("internal", "cli_version_last_modified", true, resp.lastModified); 143 | ConfigFile.set("internal", "cli_version", true, cliVersion); 144 | } 145 | return cliVersion; 146 | } 147 | } catch (Exception e) { 148 | Logger.warn(e); 149 | } 150 | cliVersion = "Unknown"; 151 | return cliVersion; 152 | } 153 | 154 | public static String getCLILocation() { 155 | if (System.getenv("WAKATIME_CLI_LOCATION") != null && !System.getenv("WAKATIME_CLI_LOCATION").trim().isEmpty()) { 156 | File wakatimeCLI = new File(System.getenv("WAKATIME_CLI_LOCATION")); 157 | if (wakatimeCLI.exists()) { 158 | Logger.debug("Using $WAKATIME_CLI_LOCATION as CLI Executable: " + wakatimeCLI); 159 | return System.getenv("WAKATIME_CLI_LOCATION"); 160 | } 161 | } 162 | 163 | String ext = isWindows() ? ".exe" : ""; 164 | return combinePaths(getResourcesLocation(), "wakatime-cli-" + osname() + "-" + architecture() + ext); 165 | } 166 | 167 | public static void installCLI() { 168 | File resourceDir = new File(getResourcesLocation()); 169 | if (!resourceDir.exists()) resourceDir.mkdirs(); 170 | 171 | checkMissingPlatformSupport(); 172 | 173 | String url = getCLIDownloadUrl(); 174 | String zipFile = combinePaths(getResourcesLocation(), "wakatime-cli.zip"); 175 | 176 | if (downloadFile(url, zipFile)) { 177 | 178 | // Delete old wakatime-cli if it exists 179 | File file = new File(getCLILocation()); 180 | recursiveDelete(file); 181 | 182 | File outputDir = new File(getResourcesLocation()); 183 | try { 184 | unzip(zipFile, outputDir); 185 | if (!isWindows()) { 186 | makeExecutable(getCLILocation()); 187 | } 188 | File oldZipFile = new File(zipFile); 189 | oldZipFile.delete(); 190 | } catch (IOException e) { 191 | Logger.warn(e); 192 | } 193 | } 194 | } 195 | 196 | private static void checkMissingPlatformSupport() { 197 | String osname = osname(); 198 | String arch = architecture(); 199 | 200 | String[] validCombinations = { 201 | "darwin-amd64", 202 | "darwin-arm64", 203 | "freebsd-386", 204 | "freebsd-amd64", 205 | "freebsd-arm", 206 | "linux-386", 207 | "linux-amd64", 208 | "linux-arm", 209 | "linux-arm64", 210 | "netbsd-386", 211 | "netbsd-amd64", 212 | "netbsd-arm", 213 | "openbsd-386", 214 | "openbsd-amd64", 215 | "openbsd-arm", 216 | "openbsd-arm64", 217 | "windows-386", 218 | "windows-amd64", 219 | "windows-arm64", 220 | }; 221 | if (!Arrays.asList(validCombinations).contains(osname + "-" + arch)) reportMissingPlatformSupport(osname, arch); 222 | } 223 | 224 | private static void reportMissingPlatformSupport(String osname, String architecture) { 225 | String url = "https://api.wakatime.com/api/v1/cli-missing?osname=" + osname + "&architecture=" + architecture + "&plugin=" + WakaTime.getDefault().IDE_NAME; 226 | try { 227 | getUrlAsString(url, null); 228 | } catch (Exception e) { 229 | Logger.warn(e); 230 | } 231 | } 232 | 233 | private static String getCLIDownloadUrl() { 234 | return "https://github.com/wakatime/wakatime-cli/releases/download/" + latestCliVersion() + "/wakatime-cli-" + osname() + "-" + architecture() + ".zip"; 235 | } 236 | 237 | public static boolean downloadFile(String url, String saveAs) { 238 | File outFile = new File(saveAs); 239 | 240 | // create output directory if does not exist 241 | File outDir = outFile.getParentFile(); 242 | if (!outDir.exists()) 243 | outDir.mkdirs(); 244 | 245 | URL downloadUrl = null; 246 | try { 247 | downloadUrl = new URL(url); 248 | } catch (MalformedURLException e) { 249 | Logger.error("DownloadFile(" + url + ") failed to init new URL"); 250 | Logger.error(e); 251 | return false; 252 | } 253 | 254 | Logger.debug("DownloadFile(" + downloadUrl.toString() + ")"); 255 | 256 | setupProxy(); 257 | 258 | ReadableByteChannel rbc = null; 259 | FileOutputStream fos = null; 260 | try { 261 | rbc = Channels.newChannel(downloadUrl.openStream()); 262 | fos = new FileOutputStream(saveAs); 263 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 264 | fos.close(); 265 | teardownProxy(); 266 | return true; 267 | } catch (RuntimeException e) { 268 | Logger.warn(e); 269 | try { 270 | // try downloading without verifying SSL cert (https://github.com/wakatime/jetbrains-wakatime/issues/46) 271 | SSLContext SSL_CONTEXT = SSLContext.getInstance("SSL"); 272 | SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null); 273 | HttpsURLConnection.setDefaultSSLSocketFactory(SSL_CONTEXT.getSocketFactory()); 274 | HttpsURLConnection conn = (HttpsURLConnection)downloadUrl.openConnection(); 275 | conn.setRequestProperty("User-Agent", "github.com/wakatime/eclipse-wakatime"); 276 | InputStream inputStream = conn.getInputStream(); 277 | fos = new FileOutputStream(saveAs); 278 | int bytesRead = -1; 279 | byte[] buffer = new byte[4096]; 280 | while ((bytesRead = inputStream.read(buffer)) != -1) { 281 | fos.write(buffer, 0, bytesRead); 282 | } 283 | inputStream.close(); 284 | fos.close(); 285 | teardownProxy(); 286 | return true; 287 | } catch (NoSuchAlgorithmException e1) { 288 | Logger.warn(e1); 289 | } catch (KeyManagementException e1) { 290 | Logger.warn(e1); 291 | } catch (IOException e1) { 292 | Logger.warn(e1); 293 | } 294 | } catch (IOException e) { 295 | Logger.warn(e); 296 | } 297 | 298 | teardownProxy(); 299 | return false; 300 | } 301 | 302 | public static Response getUrlAsString(String url, String lastModified) { 303 | StringBuilder text = new StringBuilder(); 304 | 305 | URL downloadUrl = null; 306 | try { 307 | downloadUrl = new URL(url); 308 | } catch (MalformedURLException e) { 309 | Logger.error(e); 310 | return null; 311 | } 312 | 313 | setupProxy(); 314 | 315 | String responseLastModified = null; 316 | int statusCode = -1; 317 | try { 318 | HttpsURLConnection conn = (HttpsURLConnection) downloadUrl.openConnection(); 319 | conn.setRequestProperty("User-Agent", "github.com/wakatime/eclipse-wakatime"); 320 | if (lastModified != null && !lastModified.trim().equals("")) { 321 | conn.setRequestProperty("If-Modified-Since", lastModified.trim()); 322 | } 323 | statusCode = conn.getResponseCode(); 324 | if (statusCode == 304) { 325 | teardownProxy(); 326 | return null; 327 | } 328 | InputStream inputStream = downloadUrl.openStream(); 329 | byte[] buffer = new byte[4096]; 330 | while (inputStream.read(buffer) != -1) { 331 | text.append(new String(buffer, "UTF-8")); 332 | } 333 | inputStream.close(); 334 | if (conn.getResponseCode() == 200) responseLastModified = conn.getHeaderField("Last-Modified"); 335 | } catch (RuntimeException e) { 336 | Logger.warn(e); 337 | try { 338 | // try downloading without verifying SSL cert (https://github.com/wakatime/jetbrains-wakatime/issues/46) 339 | SSLContext SSL_CONTEXT = SSLContext.getInstance("SSL"); 340 | SSL_CONTEXT.init(null, new TrustManager[]{new LocalSSLTrustManager()}, null); 341 | HttpsURLConnection.setDefaultSSLSocketFactory(SSL_CONTEXT.getSocketFactory()); 342 | HttpsURLConnection conn = (HttpsURLConnection) downloadUrl.openConnection(); 343 | conn.setRequestProperty("User-Agent", "github.com/wakatime/eclipse-wakatime"); 344 | if (lastModified != null && !lastModified.trim().equals("")) { 345 | conn.setRequestProperty("If-Modified-Since", lastModified.trim()); 346 | } 347 | statusCode = conn.getResponseCode(); 348 | if (statusCode == 304) { 349 | teardownProxy(); 350 | return null; 351 | } 352 | InputStream inputStream = conn.getInputStream(); 353 | byte[] buffer = new byte[4096]; 354 | while (inputStream.read(buffer) != -1) { 355 | text.append(new String(buffer, "UTF-8")); 356 | } 357 | inputStream.close(); 358 | if (conn.getResponseCode() == 200) responseLastModified = conn.getHeaderField("Last-Modified"); 359 | } catch (NoSuchAlgorithmException e1) { 360 | Logger.warn(e1); 361 | } catch (KeyManagementException e1) { 362 | Logger.warn(e1); 363 | } catch (UnknownHostException e1) { 364 | Logger.warn(e1); 365 | } catch (IOException e1) { 366 | Logger.warn(e1); 367 | } 368 | } catch (UnknownHostException e) { 369 | Logger.warn(e); 370 | } catch (Exception e) { 371 | Logger.warn(e); 372 | } 373 | 374 | teardownProxy(); 375 | return new Response(statusCode, text.toString(), responseLastModified); 376 | } 377 | 378 | /** 379 | * Configures a proxy if one is set in ~/.wakatime.cfg. 380 | */ 381 | private static void setupProxy() { 382 | originalProxyHost = System.getProperty("https.proxyHost"); 383 | originalProxyPort = System.getProperty("https.proxyPort"); 384 | String proxyConfig = ConfigFile.get("settings", "proxy", false); 385 | if (proxyConfig != null && !proxyConfig.trim().equals("")) { 386 | try { 387 | URI proxyUrl = new URI(proxyConfig); 388 | String userInfo = proxyUrl.getUserInfo(); 389 | if (userInfo != null) { 390 | final String user = userInfo.split(":")[0]; 391 | final String pass = userInfo.split(":")[1]; 392 | Authenticator authenticator = new Authenticator() { 393 | public PasswordAuthentication getPasswordAuthentication() { 394 | return (new PasswordAuthentication(user, pass.toCharArray())); 395 | } 396 | }; 397 | Authenticator.setDefault(authenticator); 398 | } 399 | 400 | if (!proxyUrl.getHost().trim().isEmpty()) { 401 | System.setProperty("https.proxyHost", proxyUrl.getHost()); 402 | System.setProperty("https.proxyPort", Integer.toString(proxyUrl.getPort())); 403 | } 404 | 405 | } catch (URISyntaxException e) { 406 | Logger.error("Proxy string must follow https://user:pass@host:port format: " + proxyConfig); 407 | } 408 | } 409 | } 410 | 411 | private static void teardownProxy() { 412 | if (originalProxyHost != null) System.setProperty("https.proxyHost", originalProxyHost); 413 | if (originalProxyPort != null) System.setProperty("https.proxyPort", originalProxyPort); 414 | } 415 | 416 | private static void unzip(String zipFile, File outputDir) throws IOException { 417 | if(!outputDir.exists()) 418 | outputDir.mkdirs(); 419 | 420 | byte[] buffer = new byte[1024]; 421 | ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile)); 422 | ZipEntry ze = zis.getNextEntry(); 423 | 424 | while (ze != null) { 425 | String fileName = ze.getName(); 426 | File newFile = new File(outputDir, fileName); 427 | 428 | if (ze.isDirectory()) { 429 | newFile.mkdirs(); 430 | } else { 431 | FileOutputStream fos = new FileOutputStream(newFile.getAbsolutePath()); 432 | int len; 433 | while ((len = zis.read(buffer)) > 0) { 434 | fos.write(buffer, 0, len); 435 | } 436 | fos.close(); 437 | } 438 | 439 | ze = zis.getNextEntry(); 440 | } 441 | 442 | zis.closeEntry(); 443 | zis.close(); 444 | } 445 | 446 | private static void recursiveDelete(File path) { 447 | if(path.exists()) { 448 | if (isDirectory(path)) { 449 | File[] files = path.listFiles(); 450 | for (int i = 0; i < files.length; i++) { 451 | if (isDirectory(files[i])) { 452 | recursiveDelete(files[i]); 453 | } else { 454 | files[i].delete(); 455 | } 456 | } 457 | } 458 | path.delete(); 459 | } 460 | } 461 | 462 | public static boolean isAlpha() { 463 | if (alpha != null) return alpha; 464 | String setting = ConfigFile.get("settings", "alpha", false); 465 | alpha = setting != null && setting.equals("true"); 466 | return alpha; 467 | } 468 | 469 | public static boolean is64bit() { 470 | return System.getProperty("os.arch").indexOf("64") != -1; 471 | } 472 | 473 | public static boolean isWindows() { 474 | return System.getProperty("os.name").contains("Windows"); 475 | } 476 | 477 | public static String osname() { 478 | if (isWindows()) return "windows"; 479 | String os = System.getProperty("os.name").toLowerCase(); 480 | if (os.contains("mac") || os.contains("darwin")) return "darwin"; 481 | if (os.contains("linux")) return "linux"; 482 | return os; 483 | } 484 | 485 | public static String architecture() { 486 | String arch = System.getProperty("os.arch"); 487 | if (arch.equals("aarch64")) return "arm64"; 488 | if (osname().equals("darwin") && arch.contains("arm")) return "arm64"; 489 | if (arch.contains("64")) return "amd64"; 490 | if (arch.contains("386") || arch.contains("32") || arch.contains("x86")) return "386"; 491 | return arch; 492 | } 493 | 494 | public static String combinePaths(String... args) { 495 | File path = null; 496 | for (String arg : args) { 497 | if (arg != null) { 498 | if (path == null) 499 | path = new File(arg); 500 | else 501 | path = new File(path, arg); 502 | } 503 | } 504 | if (path == null) 505 | return null; 506 | return path.toString(); 507 | } 508 | 509 | private static String githubReleasesApiUrl() { 510 | if (isAlpha()) { 511 | return "https://api.github.com/repos/wakatime/wakatime-cli/releases?per_page=1"; 512 | } 513 | return "https://api.github.com/repos/wakatime/wakatime-cli/releases/latest"; 514 | } 515 | 516 | private static void makeExecutable(String filePath) throws IOException { 517 | File file = new File(filePath); 518 | file.setExecutable(true); 519 | } 520 | 521 | private static boolean isSymLink(File filepath) { 522 | try { 523 | return Files.isSymbolicLink(filepath.toPath()); 524 | } catch(SecurityException e) { 525 | return false; 526 | } 527 | } 528 | 529 | private static boolean isDirectory(File filepath) { 530 | try { 531 | return filepath.isDirectory(); 532 | } catch(SecurityException e) { 533 | return false; 534 | } 535 | } 536 | 537 | public static void createSymlink(String source, String destination) { 538 | File sourceLink = new File(source); 539 | if (isDirectory(sourceLink)) recursiveDelete(sourceLink); 540 | if (!isWindows()) { 541 | if (!isSymLink(sourceLink)) { 542 | recursiveDelete(sourceLink); 543 | try { 544 | Files.createSymbolicLink(sourceLink.toPath(), new File(destination).toPath()); 545 | } catch (Exception e) { 546 | Logger.warn(e); 547 | try { 548 | Files.copy(new File(destination).toPath(), sourceLink.toPath(), StandardCopyOption.REPLACE_EXISTING); 549 | } catch (Exception ex) { 550 | Logger.warn(ex); 551 | } 552 | } 553 | } 554 | } else { 555 | try { 556 | Files.copy(new File(destination).toPath(), sourceLink.toPath(), StandardCopyOption.REPLACE_EXISTING); 557 | } catch (Exception e) { 558 | Logger.warn(e); 559 | } 560 | } 561 | } 562 | } 563 | --------------------------------------------------------------------------------