From 82412ace20834c311fc8a08cb595092ebc33f14d Mon Sep 17 00:00:00 2001 From: hptruong93 Date: Mon, 23 May 2022 15:15:53 -0400 Subject: [PATCH] Add logic to handle task source history. Starting without any storage yet. --- .../userDefinedTask/TaskSourceManager.java | 4 +- .../userDefinedTask/UserDefinedAction.java | 14 +++++ .../internals/TaskSourceHistory.java | 46 ++++++++++++++++ .../internals/TaskSourceHistoryEntry.java | 26 +++++++++ src/core/webui/server/UIServer.java | 2 + .../internals/tasks/GetTaskSourceHandler.java | 53 +++++++++++++++++++ .../RenderedDetailedUserDefinedAction.java | 14 +++++ .../RenderedTaskSourceHistory.java | 21 ++++++++ .../RenderedTaskSourceHistoryEntry.java | 28 ++++++++++ src/frontEnd/MainBackEndHolder.java | 11 ++++ .../fragments/task_source_history.ftlh | 16 ++++++ .../webui/templates/task_details.ftlh | 2 + 12 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 src/core/userDefinedTask/internals/TaskSourceHistory.java create mode 100644 src/core/userDefinedTask/internals/TaskSourceHistoryEntry.java create mode 100644 src/core/webui/server/handlers/internals/tasks/GetTaskSourceHandler.java create mode 100644 src/core/webui/server/handlers/renderedobjects/RenderedTaskSourceHistory.java create mode 100644 src/core/webui/server/handlers/renderedobjects/RenderedTaskSourceHistoryEntry.java create mode 100644 src/staticContent/webui/templates/fragments/task_source_history.ftlh diff --git a/src/core/userDefinedTask/TaskSourceManager.java b/src/core/userDefinedTask/TaskSourceManager.java index 4da2a501..d20e71b5 100644 --- a/src/core/userDefinedTask/TaskSourceManager.java +++ b/src/core/userDefinedTask/TaskSourceManager.java @@ -26,7 +26,9 @@ public static boolean submitTask(UserDefinedAction task, String source) { return false; } - task.setSourcePath(FileUtility.getRelativePwdPath(sourceFile)); + String relativePath = FileUtility.getRelativePwdPath(sourceFile); + task.setSourcePath(relativePath); + task.recordSourceHistory(relativePath); return true; } diff --git a/src/core/userDefinedTask/UserDefinedAction.java b/src/core/userDefinedTask/UserDefinedAction.java index e5957e09..d2624a61 100644 --- a/src/core/userDefinedTask/UserDefinedAction.java +++ b/src/core/userDefinedTask/UserDefinedAction.java @@ -18,6 +18,8 @@ import core.languageHandler.compiler.AbstractNativeCompiler; import core.languageHandler.compiler.DynamicCompilerManager; import core.languageHandler.compiler.RemoteRepeatsCompiler; +import core.userDefinedTask.internals.TaskSourceHistory; +import core.userDefinedTask.internals.TaskSourceHistoryEntry; import utilities.FileUtility; import utilities.ILoggable; import utilities.json.IJsonable; @@ -39,7 +41,9 @@ public abstract class UserDefinedAction implements IJsonable, ILoggable { // This is to enable invoking task programmatically. protected TaskInvoker taskInvoker; + protected UsageStatistics statistics; + protected TaskSourceHistory sourceHistory; public UserDefinedAction() { this(UUID.randomUUID().toString()); @@ -51,6 +55,7 @@ protected UserDefinedAction(String actionId) { invoker = TaskActivation.newBuilder().build(); invokingKeyChain = new KeyChain(); statistics = new UsageStatistics(); + sourceHistory = new TaskSourceHistory(); enabled = true; } @@ -123,6 +128,10 @@ public final void setSourcePath(String sourcePath) { this.sourcePath = sourcePath; } + public final void recordSourceHistory(String sourcePath) { + sourceHistory.addEntry(TaskSourceHistoryEntry.of(sourcePath)); + } + public final Language getCompiler() { return compiler; } @@ -151,10 +160,15 @@ public final UsageStatistics getStatistics() { return statistics; } + public final TaskSourceHistory getTaskSourceHistory() { + return sourceHistory; + } + public final void override(UserDefinedAction other) { setName(other.getName()); activation.copy(other.activation); statistics = other.statistics; + sourceHistory.addHistory(other.sourceHistory); } /** diff --git a/src/core/userDefinedTask/internals/TaskSourceHistory.java b/src/core/userDefinedTask/internals/TaskSourceHistory.java new file mode 100644 index 00000000..2594e5b7 --- /dev/null +++ b/src/core/userDefinedTask/internals/TaskSourceHistory.java @@ -0,0 +1,46 @@ +package core.userDefinedTask.internals; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** Contains information about the source history of a task. */ +public class TaskSourceHistory { + + private List entries; + + public TaskSourceHistory() { + this.entries = new ArrayList<>(); + } + + /** + * Finds a particular entry given the timestamp. + * + * @param timestamp the timestamp to find the entry for. + * @return the found entry, or null if no entry was found. + */ + public TaskSourceHistoryEntry findEntry(long timestamp) { + for (TaskSourceHistoryEntry entry : entries) { + if (entry.getCreated().getTimeInMillis() == timestamp) { + return entry; + } + } + return null; + } + + // Adds a single entry to the history. + public void addEntry(TaskSourceHistoryEntry entry) { + this.entries.add(entry); + } + + /// Adds all history to this history. + public void addHistory(TaskSourceHistory history) { + entries.addAll(history.entries); + entries.sort((e1, e2) -> e2.getCreated().compareTo(e1.getCreated())); + } + + // Returns the list of entries sorted in reverse chronological order. + public List getEntries() { + return entries.stream().sorted((e1, e2) -> e2.getCreated().compareTo(e1.getCreated())).collect(Collectors.toList()); + } +} diff --git a/src/core/userDefinedTask/internals/TaskSourceHistoryEntry.java b/src/core/userDefinedTask/internals/TaskSourceHistoryEntry.java new file mode 100644 index 00000000..e86b6532 --- /dev/null +++ b/src/core/userDefinedTask/internals/TaskSourceHistoryEntry.java @@ -0,0 +1,26 @@ +package core.userDefinedTask.internals; + +import java.util.Calendar; + +/** Holds a single entry of source history. */ +public class TaskSourceHistoryEntry { + private String sourcePath; + private Calendar created; + + private TaskSourceHistoryEntry(String sourcePath, Calendar created) { + this.sourcePath = sourcePath; + this.created = created; + } + + public static TaskSourceHistoryEntry of(String path) { + return new TaskSourceHistoryEntry(path, Calendar.getInstance()); + } + + public String getSourcePath() { + return sourcePath; + } + + public Calendar getCreated() { + return created; + } +} diff --git a/src/core/webui/server/UIServer.java b/src/core/webui/server/UIServer.java index 4345042d..5a89d129 100644 --- a/src/core/webui/server/UIServer.java +++ b/src/core/webui/server/UIServer.java @@ -111,6 +111,7 @@ import core.webui.server.handlers.internals.taskmanagement.GetRenderedTaskGroupsSelectModalHandler; import core.webui.server.handlers.internals.tasks.ActionSaveTaskActivationHandler; import core.webui.server.handlers.internals.tasks.GetRunTaskConfigHandler; +import core.webui.server.handlers.internals.tasks.GetTaskSourceHandler; import core.webui.server.handlers.internals.tasks.ModifyTaskNameHandler; import core.webui.server.handlers.internals.tasks.RunTaskHandler; import core.webui.server.handlers.internals.tasks.SaveRunTaskConfigHandler; @@ -277,6 +278,7 @@ private Map createHandlers() { output.put("/internals/get/mouse-position", new GetMousePositionHandler()); output.put("/internals/get/path-suggestion", new GetPathSuggestionHandler()); output.put("/internals/get/source-templates", new GetSourceTemplateHandler()); + output.put("/internals/get/task-source", new GetTaskSourceHandler()); output.put("/internals/get/rendered-task-groups-dropdown", new GetRenderedTaskGroupsDropdown(objectRenderer)); output.put("/internals/get/rendered-task-groups-select-modal", new GetRenderedTaskGroupsSelectModalHandler(objectRenderer)); diff --git a/src/core/webui/server/handlers/internals/tasks/GetTaskSourceHandler.java b/src/core/webui/server/handlers/internals/tasks/GetTaskSourceHandler.java new file mode 100644 index 00000000..355e9681 --- /dev/null +++ b/src/core/webui/server/handlers/internals/tasks/GetTaskSourceHandler.java @@ -0,0 +1,53 @@ +package core.webui.server.handlers.internals.tasks; + +import java.io.IOException; +import java.util.Map; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.nio.protocol.HttpAsyncExchange; +import org.apache.http.protocol.HttpContext; + +import core.userDefinedTask.UserDefinedAction; +import core.webui.server.handlers.AbstractSingleMethodHttpHandler; +import core.webui.webcommon.HttpServerUtilities; + +public class GetTaskSourceHandler extends AbstractSingleMethodHttpHandler { + + public GetTaskSourceHandler() { + super(AbstractSingleMethodHttpHandler.GET_METHOD); + } + + @Override + protected Void handleAllowedRequestWithBackend(HttpRequest request, HttpAsyncExchange exchange, HttpContext context) + throws HttpException, IOException { + String uriString = request.getRequestLine().getUri(); + Map params = HttpServerUtilities.parseGetParameters(uriString); + if (params == null) { + return HttpServerUtilities.prepareHttpResponse(exchange, 500, "Failed to parse URL " + uriString); + } + + String id = params.get("id"); + if (id == null || id.isEmpty()) { + return HttpServerUtilities.prepareHttpResponse(exchange, 400, "Task ID is empty or not provided."); + } + + String timestampString = params.get("timestamp"); + if (timestampString == null || timestampString.isEmpty()) { + return HttpServerUtilities.prepareHttpResponse(exchange, 400, "Timestamp is empty or not provided."); + } + + UserDefinedAction action = backEndHolder.getTask(id); + if (action == null) { + return HttpServerUtilities.prepareHttpResponse(exchange, 404, "No action for ID " + id + "."); + } + + Long timestamp = Long.parseLong(timestampString); + String sourceCode = backEndHolder.getSourceForTask(action, timestamp); + if (sourceCode == null) { + return HttpServerUtilities.prepareHttpResponse(exchange, 500, "No source code found for task " + id + "."); + } + + return HttpServerUtilities.prepareTextResponse(exchange, 200, sourceCode); + } +} diff --git a/src/core/webui/server/handlers/renderedobjects/RenderedDetailedUserDefinedAction.java b/src/core/webui/server/handlers/renderedobjects/RenderedDetailedUserDefinedAction.java index 6f7e283d..506049f7 100644 --- a/src/core/webui/server/handlers/renderedobjects/RenderedDetailedUserDefinedAction.java +++ b/src/core/webui/server/handlers/renderedobjects/RenderedDetailedUserDefinedAction.java @@ -10,6 +10,8 @@ public class RenderedDetailedUserDefinedAction { private RenderedTaskActivation activation; private String hasStatistics; private RenderedUserDefinedActionStatistics statistics; + private String hasSourceHistory; + private RenderedTaskSourceHistory sourceHistory; private RenderedDetailedUserDefinedAction() {} @@ -21,6 +23,8 @@ public static RenderedDetailedUserDefinedAction withEmptyTaskInfo(TaskActivation result.activation = RenderedTaskActivation.fromActivation(activationConstructor); result.hasStatistics = false + ""; result.statistics = null; + result.hasSourceHistory = false + ""; + result.sourceHistory = null; return result; } @@ -32,6 +36,8 @@ public static RenderedDetailedUserDefinedAction fromHotkey(String id, String nam result.activation = RenderedTaskActivation.fromActivation(activationConstructor); result.hasStatistics = false + ""; result.statistics = null; + result.hasSourceHistory = false + ""; + result.sourceHistory = null; return result; } @@ -43,6 +49,8 @@ public static RenderedDetailedUserDefinedAction fromUserDefinedAction(UserDefine result.activation = RenderedTaskActivation.fromActivation(activationConstructor); result.hasStatistics = true + ""; result.statistics = RenderedUserDefinedActionStatistics.fromUserDefinedActionStatistics(action.getStatistics()); + result.hasSourceHistory = true + ""; + result.sourceHistory = RenderedTaskSourceHistory.of(action.getActionId(), action.getTaskSourceHistory()); return result; } @@ -76,6 +84,12 @@ public RenderedUserDefinedActionStatistics getStatistics() { public void setStatistics(RenderedUserDefinedActionStatistics statistics) { this.statistics = statistics; } + public String getHasSourceHistory() { + return hasSourceHistory; + } + public RenderedTaskSourceHistory getSourceHistory() { + return sourceHistory; + } public String getIsEnabled() { return isEnabled; } diff --git a/src/core/webui/server/handlers/renderedobjects/RenderedTaskSourceHistory.java b/src/core/webui/server/handlers/renderedobjects/RenderedTaskSourceHistory.java new file mode 100644 index 00000000..c91b32c7 --- /dev/null +++ b/src/core/webui/server/handlers/renderedobjects/RenderedTaskSourceHistory.java @@ -0,0 +1,21 @@ +package core.webui.server.handlers.renderedobjects; + +import java.util.List; +import java.util.stream.Collectors; + +import core.userDefinedTask.internals.TaskSourceHistory; + +public class RenderedTaskSourceHistory { + + private List entries; + + public static RenderedTaskSourceHistory of(String taskId, TaskSourceHistory history) { + RenderedTaskSourceHistory result = new RenderedTaskSourceHistory(); + result.entries = history.getEntries().stream().map(e -> RenderedTaskSourceHistoryEntry.of(taskId, e)).collect(Collectors.toList()); + return result; + } + + public List getEntries() { + return entries; + } +} diff --git a/src/core/webui/server/handlers/renderedobjects/RenderedTaskSourceHistoryEntry.java b/src/core/webui/server/handlers/renderedobjects/RenderedTaskSourceHistoryEntry.java new file mode 100644 index 00000000..1a77209f --- /dev/null +++ b/src/core/webui/server/handlers/renderedobjects/RenderedTaskSourceHistoryEntry.java @@ -0,0 +1,28 @@ +package core.webui.server.handlers.renderedobjects; + +import core.userDefinedTask.internals.TaskSourceHistoryEntry; +import utilities.DateUtility; + +public class RenderedTaskSourceHistoryEntry { + private String taskId; + private String createdTime; + private String createdTimeMillis; + + public static RenderedTaskSourceHistoryEntry of(String taskId, TaskSourceHistoryEntry entry) { + RenderedTaskSourceHistoryEntry result = new RenderedTaskSourceHistoryEntry(); + result.taskId = taskId; + result.createdTime = DateUtility.calendarToTimeString(entry.getCreated()); + result.createdTimeMillis = entry.getCreated().getTimeInMillis() + ""; + return result; + } + + public String getTaskId() { + return taskId; + } + public String getCreatedTime() { + return createdTime; + } + public String getCreatedTimeMillis() { + return createdTimeMillis; + } +} diff --git a/src/frontEnd/MainBackEndHolder.java b/src/frontEnd/MainBackEndHolder.java index 47b6823e..021e33cc 100644 --- a/src/frontEnd/MainBackEndHolder.java +++ b/src/frontEnd/MainBackEndHolder.java @@ -52,6 +52,7 @@ import core.userDefinedTask.internals.RemoteRepeatsClientTools; import core.userDefinedTask.internals.RunActionConfig; import core.userDefinedTask.internals.SharedVariablesPubSubManager; +import core.userDefinedTask.internals.TaskSourceHistoryEntry; import globalListener.GlobalListenerHookController; import staticResources.BootStrapResources; import utilities.Desktop; @@ -857,6 +858,16 @@ public void switchEnableTask(UserDefinedAction action) { } } + public String getSourceForTask(UserDefinedAction action, long timestamp) { + TaskSourceHistoryEntry entry = action.getTaskSourceHistory().findEntry(timestamp); + if (entry == null) { + LOGGER.warning("No source path for action " + action.getName() + " at time " + timestamp + "."); + return null; + } + + return FileUtility.readFromFile(entry.getSourcePath()).toString(); + } + /** * Populate all tasks with task invoker to dynamically execute other tasks. */ diff --git a/src/staticContent/webui/templates/fragments/task_source_history.ftlh b/src/staticContent/webui/templates/fragments/task_source_history.ftlh new file mode 100644 index 00000000..97073fbb --- /dev/null +++ b/src/staticContent/webui/templates/fragments/task_source_history.ftlh @@ -0,0 +1,16 @@ +<#macro fragment> +<#if task.hasSourceHistory == "true"> +

Source history

+ + + <#list task.sourceHistory.entries as entry> + + + + + +
${entry.createdTime}
+ + + +<@fragment/> \ No newline at end of file diff --git a/src/staticContent/webui/templates/task_details.ftlh b/src/staticContent/webui/templates/task_details.ftlh index 4fde1ab5..fc0ddf49 100644 --- a/src/staticContent/webui/templates/task_details.ftlh +++ b/src/staticContent/webui/templates/task_details.ftlh @@ -1,5 +1,6 @@ <#import "/fragments/activations_modals.ftlh" as activations_modals_fragment> <#import "/fragments/activations.ftlh" as activations_fragment> +<#import "/fragments/task_source_history.ftlh" as task_source_history_fragment> <#import "/fragments/task_statistics.ftlh" as task_statistics_fragment> <#import "/fragments/common_header.ftlh" as common_header_fragment> <#import "/fragments/footer.ftlh" as footer_fragment> @@ -56,6 +57,7 @@ <@activations_fragment.fragment/> + <@task_source_history_fragment.fragment/> <@task_statistics_fragment.fragment/>