diff --git a/build.gradle b/build.gradle
index ba7c51c..e5ac60b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ plugins {
}
group 'fr.mary.olivier'
-version '1.0.8'
+version '2.0.0'
repositories {
@@ -21,7 +21,7 @@ sourceSets {
}
}
-task generateVersion {
+tasks.register('generateVersion') {
doFirst {
def versionFile = file("${projectDir}/src/generated/java/fr/mary/olivier/aw/watcher/Version.java")
versionFile.parentFile.mkdirs()
@@ -57,32 +57,37 @@ clean {
delete += sourceSets.generated.java.srcDirs + sourceSets.generated.resources.srcDirs + swaggerSources.activityWatcher.code.outputDir
}
-sourceCompatibility = 17
-targetCompatibility = 17
+sourceCompatibility = JavaVersion.VERSION_17
+targetCompatibility = JavaVersion.VERSION_17
dependencies {
- compileOnly 'io.swagger:swagger-annotations:1.6.9'
- compileOnly 'com.squareup.okhttp:okhttp:2.7.5'
- compileOnly 'com.squareup.okhttp:logging-interceptor:2.7.5'
- compileOnly 'com.google.code.gson:gson:2.10.1'
- compileOnly 'io.gsonfire:gson-fire:1.8.3'
- compileOnly 'org.threeten:threetenbp:1.6.5'
- compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
- compileOnly 'org.openapitools:openapi-generator:6.3.0'
- compileOnly 'org.apache.oltu.oauth2:org.apache.oltu.oauth2.common:1.0.2'
- compileOnly 'org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:1.0.2'
- testCompileOnly 'junit:junit:4.12'
+ implementation 'io.swagger:swagger-annotations:1.6.9'
+ implementation 'com.squareup.okhttp:okhttp:1.6.0'
+ implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
+ implementation 'com.google.code.gson:gson:2.10.1'
+ implementation 'io.gsonfire:gson-fire:1.8.3'
+ implementation 'javax.annotation:javax.annotation-api:1.3.2'
+ implementation 'javax.ws.rs:javax.ws.rs-api:2.1.1'
+ implementation 'org.openapitools:openapi-generator:6.3.0'
+ implementation 'org.projectlombok:lombok:1.18.24'
+ annotationProcessor 'org.projectlombok:lombok:1.18.24'
+ testImplementation 'junit:junit:4.12'
swaggerCodegen 'org.openapitools:openapi-generator-cli:6.3.0'
}
intellij {
- version.set("2022.3")
+ version.set('2022.3')
setUpdateSinceUntilBuild(false)
+ plugins.add('Git4Idea')
}
patchPluginXml {
setChangeNotes """
+ 2.0.0
+ - Add support for IntelliJ 2022.3
+ - Change to HeatBeat event
+ - Add VCS informations
1.0.8
- Fix exceptions
- Fix file name recorded
diff --git a/src/main/java/fr/mary/olivier/aw/watcher/HeartBeatData.java b/src/main/java/fr/mary/olivier/aw/watcher/HeartBeatData.java
new file mode 100755
index 0000000..e0b4c3d
--- /dev/null
+++ b/src/main/java/fr/mary/olivier/aw/watcher/HeartBeatData.java
@@ -0,0 +1,27 @@
+package fr.mary.olivier.aw.watcher;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@EqualsAndHashCode()
+@ToString(callSuper = true)
+@Builder
+@Data
+public class HeartBeatData {
+ private String file;
+ private String fileFullPath;
+ private String project;
+ private String projectPath;
+ private String language;
+ private String editor;
+ private String editorVersion;
+ private String eventType;
+ private String branch;
+ private String commit;
+ private String state;
+ private String sourceUrl;
+
+}
+
diff --git a/src/main/java/fr/mary/olivier/aw/watcher/ReportActivity.java b/src/main/java/fr/mary/olivier/aw/watcher/ReportActivity.java
index 01deb9d..694493c 100755
--- a/src/main/java/fr/mary/olivier/aw/watcher/ReportActivity.java
+++ b/src/main/java/fr/mary/olivier/aw/watcher/ReportActivity.java
@@ -11,8 +11,6 @@
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
-import com.intellij.openapi.fileEditor.FileDocumentManager;
-import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.PlatformUtils;
@@ -22,116 +20,47 @@
import fr.mary.olivier.aw.watcher.listener.RAEditorMouseListener;
import fr.mary.olivier.aw.watcher.listener.RASaveListener;
import fr.mary.olivier.aw.watcher.listener.RAVisibleAreaListener;
-import fr.mary.olivier.aw.watcher.model.EditorActivityEvent;
-import org.jetbrains.annotations.SystemIndependent;
-import org.openapitools.client.ApiException;
+import git4idea.GitUtil;
+import git4idea.repo.GitRepositoryManager;
import org.openapitools.client.api.DefaultApi;
import org.openapitools.client.model.Bucket;
import org.openapitools.client.model.CreateBucket;
import org.openapitools.client.model.Event;
-import org.threeten.bp.OffsetDateTime;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
+import java.awt.*;
import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.List;
+import java.time.OffsetDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
public class ReportActivity implements Disposable {
private static final String ACTIVITY_WATCHER = "Activity Watcher";
private static final Logger LOG = Logger.getInstance(ReportActivity.class.getName());
private static final String TYPE = "app.editor.activity";
- private static final BigDecimal MAX_STAY_TIME = new BigDecimal(2 * 60);
- private static final BigDecimal MAX_RETRY_TIME = new BigDecimal(30);
private static final String AW_WATCHER = "aw-watcher-";
- private static String ide;
- private static String ideVersion;
- private static String bucketClientNamePrefix;
- private static MessageBusConnection connection;
- private static DefaultApi apiClient;
+ private static final String IDE_NAME = PlatformUtils.getPlatformPrefix();
+ private static final String IDE_VERSION = ApplicationInfo.getInstance().getFullVersion();
+ public static final int HEARTBEAT_PULSETIME = 20;
+ public static final int CHECK_CONNEXION_DELAY = 10;
+ private static final DefaultApi API_CLIENT = new DefaultApi();
private static Bucket bucket;
- private static String lastFile = null;
- private static BigDecimal lastTime = getCurrentTimestamp();
- private static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
- private static ScheduledFuture> scheduledFixture;
+ private static final ScheduledExecutorService connexionScheduler = Executors.newScheduledThreadPool(1);
+ private static ScheduledFuture> scheduledConnexion;
private static boolean connexionFailedMessageAlreadySend = false;
private static boolean connexionLost = false;
- private static BigDecimal lastFailed = getCurrentTimestamp();
- private static List eventsToSend = new ArrayList<>();
- private static String lastFileType;
- private static Project lastProject;
+ private static MessageBusConnection connection;
+ private static ReportActivity instance;
+
- ReportActivity() {
+ private ReportActivity() {
LOG.info("Initializing ActivityWatcher plugin : Start");
- initIDEInfo();
setupConnexionToApi();
setupEventListeners();
- }
-
- private static void initIDEInfo() {
- ideVersion = ApplicationInfo.getInstance().getFullVersion();
- ide = PlatformUtils.getPlatformPrefix();
- bucketClientNamePrefix = AW_WATCHER + ide;
- }
-
- private static void setupConnexionToApi() {
- final Runnable handler = () -> {
- initClient();
-
- if (bucket == null) {
- LOG.info("Bucket null, no activity will be send.");
- LOG.info("Initializing ActivityWatcher plugin : FAIL");
- if (!connexionFailedMessageAlreadySend) {
- Notifications.Bus.notify(new Notification(ACTIVITY_WATCHER, ACTIVITY_WATCHER,
- "Activity Watcher Server not found, server is started ?\n " +
- "Will try to reconnect all 60s.", NotificationType.WARNING));
- connexionFailedMessageAlreadySend = true;
- }
- } else {
- scheduledFixture.cancel(false);
- Notifications.Bus.notify(new Notification(ACTIVITY_WATCHER, ACTIVITY_WATCHER,
- "Activity Watcher Server Connected.", NotificationType.INFORMATION));
- LOG.info("Initializing ActivityWatcher plugin : OK");
- }
- };
- scheduledFixture = scheduler.scheduleWithFixedDelay(handler, 0, 60, java.util.concurrent.TimeUnit.SECONDS);
- }
-
- private static void initClient() {
- apiClient = new DefaultApi();
-
- String hostname;
- try {
- hostname = InetAddress.getLocalHost().getHostName();
- } catch (UnknownHostException exp) {
- LOG.error("Unable to get hostname", exp);
- hostname = "unknown";
- }
-
- try {
- bucket = apiClient.getBucketResource(bucketClientNamePrefix + "_" + hostname);
- } catch (ApiException exp) {
- CreateBucket nb = new CreateBucket();
- nb.setClient(bucketClientNamePrefix);
- try {
- nb.setHostname(InetAddress.getLocalHost().getHostName());
- } catch (UnknownHostException e1) {
- nb.setHostname("unknown");
- }
- nb.setType(TYPE);
- try {
- apiClient.postBucketResource(bucketClientNamePrefix + "_" + hostname, nb);
- bucket = apiClient.getBucketResource(bucketClientNamePrefix + "_" + hostname);
- } catch (ApiException expBis) {
- LOG.warn("Unable to init bucket", expBis);
- }
- }
+ instance = this;
}
private static void setupEventListeners() {
@@ -141,132 +70,143 @@ private static void setupEventListeners() {
connection = bus.connect();
connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new RASaveListener());
// Switch document or in document
- EditorFactory.getInstance().getEventMulticaster().addDocumentListener(new RADocumentListener());
- EditorFactory.getInstance().getEventMulticaster().addEditorMouseListener(new RAEditorMouseListener());
- EditorFactory.getInstance().getEventMulticaster().addVisibleAreaListener(new RAVisibleAreaListener());
+ EditorFactory.getInstance().getEventMulticaster().addDocumentListener(new RADocumentListener(), instance);
+ EditorFactory.getInstance().getEventMulticaster().addEditorMouseListener(new RAEditorMouseListener(), instance);
+ EditorFactory.getInstance().getEventMulticaster().addVisibleAreaListener(new RAVisibleAreaListener(), instance);
});
}
- private static boolean longEnougthToLog(BigDecimal now) {
- return lastTime.add(MAX_STAY_TIME).compareTo(now) < 0;
+ public static void sendHeartBeat(VirtualFile file, Document document) {
+ sendHeartBeat(file, getProject(document));
}
- private static boolean longEnougthToRetry() {
- return lastFailed.add(MAX_RETRY_TIME).compareTo(getCurrentTimestamp()) < 0;
+ private static Project getProject(Document document) {
+ Editor[] editors = EditorFactory.getInstance().getEditors(document);
+ if (editors.length > 0) {
+ return editors[0].getProject();
+ }
+ return null;
}
- public static void addAndSendEvent(final VirtualFile file, Project project, Class clazz) {
- synchronized (eventsToSend) {
- final BigDecimal time = getCurrentTimestamp();
- if (file == null || file.getPath().equals(lastFile) && !longEnougthToLog(time)
- || eventsToSend.stream().anyMatch(e -> file.getPath().endsWith(((EditorActivityEvent) e.getData()).getFile()))) {
+ public static void sendHeartBeat(VirtualFile file, Project project) {
+ if (bucket != null && isAppActive() && !connexionLost) {
+ if (project == null || !project.isInitialized()) {
return;
}
- if (lastFile == null){
- initLastFile(file, project, time);
+ if (file == null) {
return;
}
- final BigDecimal duration = time.subtract(lastTime);
- @SystemIndependent final String projectPath = lastProject != null && lastProject.getBasePath() != null ? lastProject.getBasePath() : "";
- Event event = new Event()
- .duration(duration)
- .data(new EditorActivityEvent(
- lastFile.replace(projectPath,""),
- lastProject != null ? lastProject.getName() : null,
- projectPath,
- lastFileType,
- ide, ideVersion, clazz.getName()))
- .timestamp(OffsetDateTime.now());
- eventsToSend.add(event);
- sendAllEvents();
- initLastFile(file, project, time);
- }
- }
-
- private static void initLastFile(VirtualFile file, Project project, BigDecimal time) {
- lastProject = project;
- lastFileType = getLanguage(file);
- lastFile = file.getPath();
- lastTime = time;
- }
+ HeartBeatData.HeartBeatDataBuilder dataBuilder = HeartBeatData.builder()
+ .projectPath(project.getPresentableUrl())
+ .editorVersion(IDE_VERSION)
+ .editor(IDE_NAME)
+ //.eventType(classz.getName()) disabled for heartbeat because data change and create multiples of event
+ .file(file.getPresentableName())
+ .fileFullPath(file.getPath())
+ .project(project.getName())
+ .language(file.getFileType().getName());
+ GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(project);
+ repositoryManager.getRepositories().stream().findFirst().ifPresent(r -> {
+ dataBuilder.branch(r.getCurrentBranchName());
+ dataBuilder.commit(r.getCurrentRevision());
+ dataBuilder.state(r.getState().name());
+ r.getInfo().getRemotes().stream().findFirst().ifPresent(gitRemote -> dataBuilder.sourceUrl(gitRemote.getFirstUrl()));
+ });
+
+ HeartBeatData data = dataBuilder.build();
+ Event event = new Event().data(data).timestamp(OffsetDateTime.now());
- static void eventSent(Event event) {
- synchronized (eventsToSend) {
- eventsToSend.remove(event);
- }
- }
-
- private static void sendAllEvents() {
- if (bucket == null || ReportActivity.isConnexionLost() && !longEnougthToRetry()) {
- return;
- }
- synchronized (eventsToSend) {
- for (Event event : eventsToSend) {
- try {
- apiClient.postEventsResourceAsync(bucket.getId(), event, new ReportActivityCallBack(event));
- } catch (ApiException exp) {
- // nothing
- }
+ try {
+ API_CLIENT.postHeartbeatResource(bucket.getId(), event, "" + (HEARTBEAT_PULSETIME));
+ } catch (Exception e) {
+ LOG.error("Unable to send heartbeat", e);
+ ReportActivity.connexionLost();
}
}
}
- public static Project getProject(Document document) {
- Editor[] editors = EditorFactory.getInstance().getEditors(document);
- if (editors.length > 0) {
- return editors[0].getProject();
+ private static void initBucket() {
+ String bucketClientNamePrefix = AW_WATCHER + IDE_NAME;
+ String hostname;
+ try {
+ hostname = InetAddress.getLocalHost().getHostName();
+ } catch (Exception exp) {
+ LOG.error("Unable to get hostname", exp);
+ hostname = "unknown";
}
- return null;
- }
- static void connexionResume() {
- if (ReportActivity.isConnexionLost()) {
- ReportActivity.setConnexionLost(false);
- Notifications.Bus.notify(new Notification(ReportActivity.ACTIVITY_WATCHER, ReportActivity.ACTIVITY_WATCHER,
- "Activity Watcher Server is back!", NotificationType.INFORMATION));
+ try {
+ bucket = API_CLIENT.getBucketResource(bucketClientNamePrefix + "_" + hostname);
+ } catch (Exception exp) {
+ CreateBucket nb = new CreateBucket();
+ nb.setClient(bucketClientNamePrefix);
+ try {
+ nb.setHostname(InetAddress.getLocalHost().getHostName());
+ } catch (Exception e1) {
+ nb.setHostname("unknown");
+ }
+ nb.setType(TYPE);
+ try {
+ API_CLIENT.postBucketResource(bucketClientNamePrefix + "_" + hostname, nb);
+ bucket = API_CLIENT.getBucketResource(bucketClientNamePrefix + "_" + hostname);
+ } catch (Exception expBis) {
+ LOG.warn("Unable to init bucket", expBis);
+ connexionLost = true;
+ }
}
}
- static void connexionLost() {
- if (!ReportActivity.isConnexionLost()) {
- Notifications.Bus.notify(new Notification(ReportActivity.ACTIVITY_WATCHER, ReportActivity.ACTIVITY_WATCHER,
- "Activity Watcher Server connexion lost?\n " +
- "Will try to re-send events when connexion back.", NotificationType.WARNING));
- }
- ReportActivity.setConnexionLost(true);
- lastFailed = getCurrentTimestamp();
- }
+ private static void setupConnexionToApi() {
- private static synchronized boolean isConnexionLost() {
- return connexionLost;
- }
+ final Runnable handler = () -> {
+ if (bucket == null) {
+ initBucket();
+ }
- private static synchronized void setConnexionLost(boolean connexionLost) {
- ReportActivity.connexionLost = connexionLost;
- }
+ if (bucket == null && !connexionFailedMessageAlreadySend) {
+ connexionLost();
+ }
- private static String getLanguage(final VirtualFile file) {
- FileType type = file.getFileType();
- return type.getName();
+ if (bucket != null && connexionLost) {
+ connexionResume();
+ }
+ };
+ scheduledConnexion = connexionScheduler.scheduleWithFixedDelay(handler, 0, CHECK_CONNEXION_DELAY, TimeUnit.SECONDS);
}
- private static BigDecimal getCurrentTimestamp() {
- return new BigDecimal(String.valueOf(System.currentTimeMillis() / 1000.0)).setScale(4, RoundingMode.HALF_UP);
+ private static boolean isAppActive() {
+ return KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow() != null;
}
@Override
public void dispose() {
- sendAllEvents(); // Try to send lasts event if connexion failed.
try {
connection.disconnect();
} catch (Exception e) {
- LOG.error("Unable to disconnect", e);
+ LOG.error("Unable to disconnect to message bus", e);
}
try {
- scheduledFixture.cancel(true);
+ scheduledConnexion.cancel(true);
} catch (Exception e) {
- LOG.error("Unable to cancel scheduler", e);
+ LOG.error("Unable to cancel schedulers", e);
+ }
+ }
+
+ private static synchronized void connexionResume() {
+ if (connexionLost) {
+ connexionLost = false;
+ connexionFailedMessageAlreadySend = false;
+ Notifications.Bus.notify(new Notification(ReportActivity.ACTIVITY_WATCHER, ReportActivity.ACTIVITY_WATCHER, "Activity Watcher Server is back!", NotificationType.INFORMATION));
+ }
+ }
+
+ protected static synchronized void connexionLost() {
+ if (!connexionFailedMessageAlreadySend) {
+ connexionFailedMessageAlreadySend = true;
+ Notifications.Bus.notify(new Notification(ReportActivity.ACTIVITY_WATCHER, ReportActivity.ACTIVITY_WATCHER, "Activity Watcher Server connexion lost?\nWill try to re-send events when connexion back.", NotificationType.WARNING));
}
+ connexionLost = true;
+ bucket = null;
}
}
diff --git a/src/main/java/fr/mary/olivier/aw/watcher/ReportActivityCallBack.java b/src/main/java/fr/mary/olivier/aw/watcher/ReportActivityCallBack.java
deleted file mode 100755
index aedc05d..0000000
--- a/src/main/java/fr/mary/olivier/aw/watcher/ReportActivityCallBack.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package fr.mary.olivier.aw.watcher;
-
-
-import org.openapitools.client.ApiCallback;
-import org.openapitools.client.ApiException;
-import org.openapitools.client.model.Event;
-
-import java.util.List;
-import java.util.Map;
-
-public class ReportActivityCallBack implements ApiCallback {
-
- private Event event;
-
- ReportActivityCallBack(Event event) {
- this.event = event;
- }
-
- @Override
- public void onFailure(ApiException exp, int statusCode, Map> responseHeaders) {
- ReportActivity.connexionLost();
- }
-
- @Override
- public void onSuccess(Void result, int statusCode, Map> responseHeaders) {
- ReportActivity.connexionResume();
- ReportActivity.eventSent(event);
- }
-
- @Override
- public void onUploadProgress(long bytesWritten, long contentLength, boolean done) {
- // no action
-
- }
-
- @Override
- public void onDownloadProgress(long bytesRead, long contentLength, boolean done) {
- // no action
- }
-}
diff --git a/src/main/java/fr/mary/olivier/aw/watcher/listener/RADocumentListener.java b/src/main/java/fr/mary/olivier/aw/watcher/listener/RADocumentListener.java
old mode 100755
new mode 100644
index de8bf05..5e65d96
--- a/src/main/java/fr/mary/olivier/aw/watcher/listener/RADocumentListener.java
+++ b/src/main/java/fr/mary/olivier/aw/watcher/listener/RADocumentListener.java
@@ -19,6 +19,6 @@ public void beforeDocumentChange(@NotNull DocumentEvent documentEvent) {
public void documentChanged(@NotNull DocumentEvent documentEvent) {
Document document = documentEvent.getDocument();
VirtualFile file = FileDocumentManager.getInstance().getFile(document);
- ReportActivity.addAndSendEvent(file, ReportActivity.getProject(document), RADocumentListener.class);
+ ReportActivity.sendHeartBeat(file, document);
}
}
diff --git a/src/main/java/fr/mary/olivier/aw/watcher/listener/RAEditorMouseListener.java b/src/main/java/fr/mary/olivier/aw/watcher/listener/RAEditorMouseListener.java
old mode 100755
new mode 100644
index 82530b5..e15658d
--- a/src/main/java/fr/mary/olivier/aw/watcher/listener/RAEditorMouseListener.java
+++ b/src/main/java/fr/mary/olivier/aw/watcher/listener/RAEditorMouseListener.java
@@ -1,6 +1,5 @@
package fr.mary.olivier.aw.watcher.listener;
-
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.EditorMouseEvent;
import com.intellij.openapi.editor.event.EditorMouseListener;
@@ -16,7 +15,7 @@ public void mousePressed(@NotNull EditorMouseEvent editorMouseEvent) {
Editor editor = editorMouseEvent.getEditor();
VirtualFile file = FileDocumentManager.getInstance().getFile(editor.getDocument());
Project project = editor.getProject();
- ReportActivity.addAndSendEvent(file, project, RAEditorMouseListener.class);
+ ReportActivity.sendHeartBeat(file, project);
}
@Override
diff --git a/src/main/java/fr/mary/olivier/aw/watcher/listener/RASaveListener.java b/src/main/java/fr/mary/olivier/aw/watcher/listener/RASaveListener.java
old mode 100755
new mode 100644
index 8866553..ca76924
--- a/src/main/java/fr/mary/olivier/aw/watcher/listener/RASaveListener.java
+++ b/src/main/java/fr/mary/olivier/aw/watcher/listener/RASaveListener.java
@@ -1,6 +1,5 @@
package fr.mary.olivier.aw.watcher.listener;
-
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileDocumentManagerListener;
@@ -13,7 +12,7 @@ public class RASaveListener implements FileDocumentManagerListener {
@Override
public void beforeDocumentSaving(@NotNull Document document) {
VirtualFile file = FileDocumentManager.getInstance().getFile(document);
- ReportActivity.addAndSendEvent(file, ReportActivity.getProject(document), RASaveListener.class);
+ ReportActivity.sendHeartBeat(file, document);
}
@Override
diff --git a/src/main/java/fr/mary/olivier/aw/watcher/listener/RAVisibleAreaListener.java b/src/main/java/fr/mary/olivier/aw/watcher/listener/RAVisibleAreaListener.java
old mode 100755
new mode 100644
index 47587ff..d257d2f
--- a/src/main/java/fr/mary/olivier/aw/watcher/listener/RAVisibleAreaListener.java
+++ b/src/main/java/fr/mary/olivier/aw/watcher/listener/RAVisibleAreaListener.java
@@ -1,6 +1,5 @@
package fr.mary.olivier.aw.watcher.listener;
-
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.VisibleAreaEvent;
import com.intellij.openapi.editor.event.VisibleAreaListener;
@@ -16,6 +15,6 @@ public void visibleAreaChanged(@NotNull VisibleAreaEvent visibleAreaEvent) {
Editor editor = visibleAreaEvent.getEditor();
VirtualFile file = FileDocumentManager.getInstance().getFile(editor.getDocument());
Project project = editor.getProject();
- ReportActivity.addAndSendEvent(file, project, RAVisibleAreaListener.class);
+ ReportActivity.sendHeartBeat(file, project);
}
}
\ No newline at end of file
diff --git a/src/main/java/fr/mary/olivier/aw/watcher/model/EditorActivityEvent.java b/src/main/java/fr/mary/olivier/aw/watcher/model/EditorActivityEvent.java
deleted file mode 100755
index a950e20..0000000
--- a/src/main/java/fr/mary/olivier/aw/watcher/model/EditorActivityEvent.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package fr.mary.olivier.aw.watcher.model;
-
-import com.google.gson.annotations.SerializedName;
-
-public class EditorActivityEvent {
- @SerializedName("file")
- private String file;
-
- @SerializedName("project")
- private String project;
-
- @SerializedName("projectPath")
- private String projectPath;
-
- @SerializedName("language")
- private String language;
-
- @SerializedName("editor")
- private String editor;
-
- @SerializedName("editorVersion")
- private String editorVersion;
-
- @SerializedName("eventType")
- private String eventType;
-
- public EditorActivityEvent(String file, String project, String projectPath, String language, String editor, String editorVersion, String eventType) {
- this.file = file;
- this.project = project;
- this.projectPath = projectPath;
- this.language = language;
- this.editor = editor;
- this.editorVersion = editorVersion;
- this.eventType = eventType;
- }
-
- public String getFile() {
- return file;
- }
-
- public void setFile(String file) {
- this.file = file;
- }
-
- public String getProject() {
- return project;
- }
-
- public void setProject(String project) {
- this.project = project;
- }
-
- public String getProjectPath() {
- return projectPath;
- }
-
- public void setProjectPath(String projectPath) {
- this.projectPath = projectPath;
- }
-
- public String getLanguage() {
- return language;
- }
-
- public void setLanguage(String language) {
- this.language = language;
- }
-
- public String getEditor() {
- return editor;
- }
-
- public void setEditor(String editor) {
- this.editor = editor;
- }
-
- public String getEditorVersion() {
- return editorVersion;
- }
-
- public void setEditorVersion(String editorVersion) {
- this.editorVersion = editorVersion;
- }
-
- public String getEventType() {
- return eventType;
- }
-
- public void setEventType(String eventType) {
- this.eventType = eventType;
- }
-
- @Override
- public String toString() {
- return "EditorActivityEvent{" +
- "file='" + file + '\'' +
- ", project='" + project + '\'' +
- ", projectPath='" + projectPath + '\'' +
- ", language='" + language + '\'' +
- ", editor='" + editor + '\'' +
- ", editorVersion='" + editorVersion + '\'' +
- ", eventType='" + eventType + '\'' +
- '}';
- }
-}
-
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 39661df..5682674 100755
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -2,16 +2,17 @@
fr.mary.olivier.aw-watcher
Activity Watcher
OlivierMARY
- 1.0.8
+ 2.0.0
-
+
com.intellij.modules.lang
com.intellij.modules.platform
+ Git4Idea
@@ -24,7 +25,7 @@
+ text="Activity Watcher Informations" description="Activity watcher informations">
\ No newline at end of file
diff --git a/src/main/resources/swagger.yaml b/src/main/resources/swagger.yaml
index b957f41..b4bb598 100755
--- a/src/main/resources/swagger.yaml
+++ b/src/main/resources/swagger.yaml
@@ -1,387 +1,390 @@
-swagger: '2.0'
-basePath: localhost:5600/api
-paths:
- /0/buckets/:
- get:
- responses:
- '200':
- description: Success
- summary: 'Get dict {bucket_name: Bucket} of all buckets'
- operationId: get_buckets_resource
- tags:
- - default
- '/0/buckets/{bucket_id}':
- parameters:
- - name: bucket_id
- in: path
- required: true
- type: string
- get:
- responses:
- '200':
- description: Success
- schema:
- $ref: '#/definitions/Bucket'
- summary: Get metadata about bucket
- operationId: get_bucket_resource
- tags:
- - default
- post:
- responses:
- '200':
- description: Success
- summary: Create bucket
- description: 'Returns True if successful, otherwise false if a bucket with the given ID already existed.'
- operationId: post_bucket_resource
- parameters:
- - name: payload
- required: true
- in: body
- schema:
- $ref: '#/definitions/CreateBucket'
- tags:
- - default
- delete:
- responses:
- '200':
- description: Success
- summary: Delete a bucket
- operationId: delete_bucket_resource
- parameters:
- - in: query
- description: Needs to be =1 to delete a bucket it non-testing mode
- name: force
- type: string
- tags:
- - default
- '/0/buckets/{bucket_id}/events':
- parameters:
- - name: bucket_id
- in: path
- required: true
- type: string
- get:
- responses:
- '200':
- description: Success
- schema:
- $ref: '#/definitions/Event'
- summary: Get events from a bucket
- operationId: get_events_resource
- parameters:
- - in: query
- description: End date of events
- name: end
- type: string
- - in: query
- description: Start date of events
- name: start
- type: string
- - in: query
- description: the maximum number of requests to get
- name: limit
- type: string
- tags:
- - default
- post:
- responses:
- '200':
- description: Success
- summary: Create events for a bucket
- description: |-
- Can handle both single events and multiple ones.
-
- Returns the inserted event when a single event was inserted, otherwise None.
- operationId: post_events_resource
- parameters:
- - name: payload
- required: true
- in: body
- schema:
- $ref: '#/definitions/Event'
- tags:
- - default
- '/0/buckets/{bucket_id}/events/count':
- parameters:
- - name: bucket_id
- in: path
- required: true
- type: string
- get:
- responses:
- '200':
- description: Success
- schema:
- type: integer
- summary: Get eventcount from a bucket
- operationId: get_event_count_resource
- parameters:
- - in: query
- description: End date of eventcount
- name: end
- type: string
- - in: query
- description: Start date of eventcount
- name: start
- type: string
- tags:
- - default
- '/0/buckets/{bucket_id}/events/{event_id}':
- parameters:
- - name: bucket_id
- in: path
- required: true
- type: string
- - name: event_id
- in: path
- required: true
- type: string
- delete:
- responses:
- '200':
- description: Success
- summary: Delete a single event from a bucket
- operationId: delete_event_resource
- tags:
- - default
- '/0/buckets/{bucket_id}/export':
- parameters:
- - name: bucket_id
- in: path
- required: true
- type: string
- get:
- responses:
- '200':
- description: Success
- schema:
- $ref: '#/definitions/Export'
- summary: 'Export a bucket to a dataformat consistent across versions, including all events in it'
- operationId: get_bucket_export_resource
- tags:
- - default
- '/0/buckets/{bucket_id}/heartbeat':
- parameters:
- - name: bucket_id
- in: path
- required: true
- type: string
- post:
- responses:
- '200':
- description: Success
- summary: Heartbeats are useful when implementing watchers that simply keep
- description: |-
- track of a state, how long it's in that state and when it changes.
- A single heartbeat always has a duration of zero.
-
- If the heartbeat was identical to the last (apart from timestamp), then the last event has its duration updated.
- If the heartbeat differed, then a new event is created.
-
- Such as:
- - Active application and window title
- - Example: aw-watcher-window
- - Currently open document/browser tab/playing song
- - Example: wakatime
- - Example: aw-watcher-web
- - Example: aw-watcher-spotify
- - Is the user active/inactive?
- Send an event on some interval indicating if the user is active or not.
- - Example: aw-watcher-afk
-
- Inspired by: https://wakatime.com/developers#heartbeats
- operationId: post_heartbeat_resource
- parameters:
- - name: payload
- required: true
- in: body
- schema:
- $ref: '#/definitions/Event'
- - in: query
- description: Largest timewindow allowed between heartbeats for them to merge
- name: pulsetime
- type: string
- tags:
- - default
- /0/export:
- get:
- responses:
- '200':
- description: Success
- schema:
- $ref: '#/definitions/Export'
- summary: Exports all buckets and their events to a format consistent across versions
- operationId: get_export_all_resource
- tags:
- - default
- /0/import:
- post:
- responses:
- '200':
- description: Success
- operationId: post_import_all_resource
- parameters:
- - name: payload
- required: true
- in: body
- schema:
- $ref: '#/definitions/Export'
- tags:
- - default
- /0/info:
- get:
- responses:
- '200':
- description: Success
- schema:
- $ref: '#/definitions/Info'
- summary: Get server info
- operationId: get_info_resource
- parameters:
- - name: X-Fields
- in: header
- type: string
- format: mask
- description: An optional fields mask
- tags:
- - default
- /0/log:
- get:
- responses:
- '200':
- description: Success
- summary: Get the server log in json format
- operationId: get_log_resource
- tags:
- - default
- /0/query/:
- post:
- responses:
- '200':
- description: Success
- operationId: post_query_resource
- parameters:
- - name: payload
- required: true
- in: body
- schema:
- $ref: '#/definitions/Query'
- - in: query
- description: Name of the query (required if using cache)
- name: name
- type: string
- tags:
- - default
-info:
- title: API
- version: '1.0'
-produces:
- - application/json
-consumes:
- - application/json
-tags:
- - name: default
- description: Default namespace
-definitions:
- Info:
- properties:
- hostname:
- type: string
- version:
- type: string
- testing:
- type: boolean
- type: object
- CreateBucket:
- required:
- - client
- - hostname
- - type
- properties:
- client:
- type: string
- type:
- type: string
- hostname:
- type: string
- type: object
- Bucket:
- title: Bucket
- description: The Bucket model that is used in ActivityWatch
- type: object
- required:
- - id
- - type
- - client
- - hostname
- properties:
- id:
- description: The unique id for the bucket
- type: string
- name:
- description: The readable and renameable name for the bucket
- type: string
- type:
- description: The event type
- type: string
- client:
- description: The name of the client that is reporting to the bucket
- type: string
- hostname:
- description: The hostname of the machine on which the client is running
- type: string
- created:
- description: The creation datetime of the bucket
- type: string
- format: date-time
- data:
- description: ''
- type: object
- events:
- type: array
- items:
- $ref: '#/definitions/Event'
- Event:
- title: Event
- description: The Event model that is used in ActivityWatch
- type: object
- required:
- - timestamp
- properties:
- timestamp:
- type: string
- format: date-time
- duration:
- type: number
- data:
- type: object
- Query:
- required:
- - query
- - timeperiods
- properties:
- timeperiods:
- type: array
- description: List of periods to query
- items:
- type: string
- query:
- type: array
- description: String list of query statements
- items:
- type: string
- type: object
- Export:
- title: Export
- description: The Export model that is used by ActivityWatch
- type: object
- properties:
- buckets:
- type: array
- items:
- $ref: '#/definitions/Bucket'
-responses:
- ParseError:
- description: When a mask can't be parsed
- MaskError:
- description: When any error occurs on mask
+swagger: '2.0'
+basePath: /api
+schemes:
+ - http
+host: localhost:5600
+paths:
+ /0/buckets/:
+ get:
+ responses:
+ '200':
+ description: Success
+ summary: 'Get dict {bucket_name: Bucket} of all buckets'
+ operationId: get_buckets_resource
+ tags:
+ - default
+ '/0/buckets/{bucket_id}':
+ parameters:
+ - name: bucket_id
+ in: path
+ required: true
+ type: string
+ get:
+ responses:
+ '200':
+ description: Success
+ schema:
+ $ref: '#/definitions/Bucket'
+ summary: Get metadata about bucket
+ operationId: get_bucket_resource
+ tags:
+ - default
+ post:
+ responses:
+ '200':
+ description: Success
+ summary: Create bucket
+ description: 'Returns True if successful, otherwise false if a bucket with the given ID already existed.'
+ operationId: post_bucket_resource
+ parameters:
+ - name: payload
+ required: true
+ in: body
+ schema:
+ $ref: '#/definitions/CreateBucket'
+ tags:
+ - default
+ delete:
+ responses:
+ '200':
+ description: Success
+ summary: Delete a bucket
+ operationId: delete_bucket_resource
+ parameters:
+ - in: query
+ description: Needs to be =1 to delete a bucket it non-testing mode
+ name: force
+ type: string
+ tags:
+ - default
+ '/0/buckets/{bucket_id}/events':
+ parameters:
+ - name: bucket_id
+ in: path
+ required: true
+ type: string
+ get:
+ responses:
+ '200':
+ description: Success
+ schema:
+ $ref: '#/definitions/Event'
+ summary: Get events from a bucket
+ operationId: get_events_resource
+ parameters:
+ - in: query
+ description: End date of events
+ name: end
+ type: string
+ - in: query
+ description: Start date of events
+ name: start
+ type: string
+ - in: query
+ description: the maximum number of requests to get
+ name: limit
+ type: string
+ tags:
+ - default
+ post:
+ responses:
+ '200':
+ description: Success
+ summary: Create events for a bucket
+ description: |-
+ Can handle both single events and multiple ones.
+
+ Returns the inserted event when a single event was inserted, otherwise None.
+ operationId: post_events_resource
+ parameters:
+ - name: payload
+ required: true
+ in: body
+ schema:
+ $ref: '#/definitions/Event'
+ tags:
+ - default
+ '/0/buckets/{bucket_id}/events/count':
+ parameters:
+ - name: bucket_id
+ in: path
+ required: true
+ type: string
+ get:
+ responses:
+ '200':
+ description: Success
+ schema:
+ type: integer
+ summary: Get eventcount from a bucket
+ operationId: get_event_count_resource
+ parameters:
+ - in: query
+ description: End date of eventcount
+ name: end
+ type: string
+ - in: query
+ description: Start date of eventcount
+ name: start
+ type: string
+ tags:
+ - default
+ '/0/buckets/{bucket_id}/events/{event_id}':
+ parameters:
+ - name: bucket_id
+ in: path
+ required: true
+ type: string
+ - name: event_id
+ in: path
+ required: true
+ type: string
+ delete:
+ responses:
+ '200':
+ description: Success
+ summary: Delete a single event from a bucket
+ operationId: delete_event_resource
+ tags:
+ - default
+ '/0/buckets/{bucket_id}/export':
+ parameters:
+ - name: bucket_id
+ in: path
+ required: true
+ type: string
+ get:
+ responses:
+ '200':
+ description: Success
+ schema:
+ $ref: '#/definitions/Export'
+ summary: 'Export a bucket to a dataformat consistent across versions, including all events in it'
+ operationId: get_bucket_export_resource
+ tags:
+ - default
+ '/0/buckets/{bucket_id}/heartbeat':
+ parameters:
+ - name: bucket_id
+ in: path
+ required: true
+ type: string
+ post:
+ responses:
+ '200':
+ description: Success
+ summary: Heartbeats are useful when implementing watchers that simply keep
+ description: |-
+ track of a state, how long it's in that state and when it changes.
+ A single heartbeat always has a duration of zero.
+
+ If the heartbeat was identical to the last (apart from timestamp), then the last event has its duration updated.
+ If the heartbeat differed, then a new event is created.
+
+ Such as:
+ - Active application and window title
+ - Example: aw-watcher-window
+ - Currently open document/browser tab/playing song
+ - Example: wakatime
+ - Example: aw-watcher-web
+ - Example: aw-watcher-spotify
+ - Is the user active/inactive?
+ Send an event on some interval indicating if the user is active or not.
+ - Example: aw-watcher-afk
+
+ Inspired by: https://wakatime.com/developers#heartbeats
+ operationId: post_heartbeat_resource
+ parameters:
+ - name: payload
+ required: true
+ in: body
+ schema:
+ $ref: '#/definitions/Event'
+ - in: query
+ description: Largest timewindow allowed between heartbeats for them to merge
+ name: pulsetime
+ type: string
+ tags:
+ - default
+ /0/export:
+ get:
+ responses:
+ '200':
+ description: Success
+ schema:
+ $ref: '#/definitions/Export'
+ summary: Exports all buckets and their events to a format consistent across versions
+ operationId: get_export_all_resource
+ tags:
+ - default
+ /0/import:
+ post:
+ responses:
+ '200':
+ description: Success
+ operationId: post_import_all_resource
+ parameters:
+ - name: payload
+ required: true
+ in: body
+ schema:
+ $ref: '#/definitions/Export'
+ tags:
+ - default
+ /0/info:
+ get:
+ responses:
+ '200':
+ description: Success
+ schema:
+ $ref: '#/definitions/Info'
+ summary: Get server info
+ operationId: get_info_resource
+ parameters:
+ - name: X-Fields
+ in: header
+ type: string
+ format: mask
+ description: An optional fields mask
+ tags:
+ - default
+ /0/log:
+ get:
+ responses:
+ '200':
+ description: Success
+ summary: Get the server log in json format
+ operationId: get_log_resource
+ tags:
+ - default
+ /0/query/:
+ post:
+ responses:
+ '200':
+ description: Success
+ operationId: post_query_resource
+ parameters:
+ - name: payload
+ required: true
+ in: body
+ schema:
+ $ref: '#/definitions/Query'
+ - in: query
+ description: Name of the query (required if using cache)
+ name: name
+ type: string
+ tags:
+ - default
+info:
+ title: API
+ version: '1.0'
+produces:
+ - application/json
+consumes:
+ - application/json
+tags:
+ - name: default
+ description: Default namespace
+definitions:
+ Info:
+ properties:
+ hostname:
+ type: string
+ version:
+ type: string
+ testing:
+ type: boolean
+ type: object
+ CreateBucket:
+ required:
+ - client
+ - hostname
+ - type
+ properties:
+ client:
+ type: string
+ type:
+ type: string
+ hostname:
+ type: string
+ type: object
+ Bucket:
+ title: Bucket
+ description: The Bucket model that is used in ActivityWatch
+ type: object
+ required:
+ - id
+ - type
+ - client
+ - hostname
+ properties:
+ id:
+ description: The unique id for the bucket
+ type: string
+ name:
+ description: The readable and renameable name for the bucket
+ type: string
+ type:
+ description: The event type
+ type: string
+ client:
+ description: The name of the client that is reporting to the bucket
+ type: string
+ hostname:
+ description: The hostname of the machine on which the client is running
+ type: string
+ created:
+ description: The creation datetime of the bucket
+ type: string
+ format: date-time
+ data:
+ description: ''
+ type: object
+ events:
+ type: array
+ items:
+ $ref: '#/definitions/Event'
+ Event:
+ title: Event
+ description: The Event model that is used in ActivityWatch
+ type: object
+ required:
+ - timestamp
+ properties:
+ timestamp:
+ type: string
+ format: date-time
+ duration:
+ type: number
+ data:
+ type: object
+ Query:
+ required:
+ - query
+ - timeperiods
+ properties:
+ timeperiods:
+ type: array
+ description: List of periods to query
+ items:
+ type: string
+ query:
+ type: array
+ description: String list of query statements
+ items:
+ type: string
+ type: object
+ Export:
+ title: Export
+ description: The Export model that is used by ActivityWatch
+ type: object
+ properties:
+ buckets:
+ type: array
+ items:
+ $ref: '#/definitions/Bucket'
+responses:
+ ParseError:
+ description: When a mask can't be parsed
+ MaskError:
+ description: When any error occurs on mask