diff --git a/src/main/java/com/collectionlogmaster/CollectionLogMasterConfig.java b/src/main/java/com/collectionlogmaster/CollectionLogMasterConfig.java index 27632bf7..8e74a950 100644 --- a/src/main/java/com/collectionlogmaster/CollectionLogMasterConfig.java +++ b/src/main/java/com/collectionlogmaster/CollectionLogMasterConfig.java @@ -11,10 +11,55 @@ public interface CollectionLogMasterConfig extends Config { String CONFIG_GROUP = "collection-log-master"; + String TASK_APP_SECTION = "taskApp"; + String TASK_APP_USERNAME = TASK_APP_SECTION + ".username"; + String TASK_APP_PASSWORD = TASK_APP_SECTION + ".password"; + + String GENERAL_SECTION = "general"; + String PLUGIN_VERSION_KEY = "plugin-version"; - String IS_LMS_ENABLED_KEY = "isLMSEnabled"; String IS_COMMAND_ENABLED_KEY = "isCommandEnabled"; + @ConfigSection( + name = "TaskApp Credentials", + description = "The credentials for your account in TaskApp (osrstaskapp.com)", + position = 10 + ) + String taskAppSection = TASK_APP_SECTION; + + @ConfigItem( + keyName = TASK_APP_USERNAME, + name = "Username", + description = "Your account's username in TaskApp (osrstaskapp.com)", + section = taskAppSection, + position = 11 + ) + default String username() + { + return ""; + } + + @ConfigItem( + keyName = TASK_APP_PASSWORD, + name = "Password", + description = "Your account's password in TaskApp (osrstaskapp.com)", + section = taskAppSection, + secret = true, + position = 12 + ) + default String password() + { + return ""; + } + + + @ConfigSection( + name = "General", + description = "General settings to control the plugin's behavior and appearance", + position = 20 + ) + String generalSection = GENERAL_SECTION; + @Range( min = 1000, max = 10000 @@ -24,7 +69,8 @@ public interface CollectionLogMasterConfig extends Config keyName = "rollTime", name = "Roll Time", description = "How long new tasks will take to roll", - position = 1 + section = generalSection, + position = 21 ) default int rollTime() { @@ -34,8 +80,9 @@ default int rollTime() @ConfigItem( keyName = "rollPastCompleted", name = "Roll past completed", - description = "When rolling tasks, include those you've already completed in the roll animation. Helpful when you're getting to the end of a tier!", - position = 2 + description = "Include tasks you've already completed in the roll animation. Helpful when you're getting to the end of a tier!", + section = generalSection, + position = 22 ) default boolean rollPastCompleted() { @@ -46,7 +93,8 @@ default boolean rollPastCompleted() keyName = "hideBelow", name = "Hide Tasks Below", description = "Disabled the showing up/assigning of tasks at or below the specified tier", - position = 3 + section = generalSection, + position = 23 ) default TaskTier hideBelow() { @@ -57,7 +105,8 @@ default TaskTier hideBelow() keyName = "displayCurrentTaskOverlay", name = "Display current task overlay", description = "Enable an overlay showing the currently assigned task (when one exists)", - position = 5 + section = generalSection, + position = 24 ) default boolean displayCurrentTaskOverlay() { @@ -68,37 +117,27 @@ default boolean displayCurrentTaskOverlay() keyName = "dynamicTaskImages", name = "Dynamic task images", description = "Display dynamic task images based on required/acquired items", - position = 6 + section = generalSection, + position = 25 ) default DynamicTaskImages dynamicTaskImages() { return DynamicTaskImages.COMPLETE; } - @ConfigItem( - keyName = IS_LMS_ENABLED_KEY, - name = "Enable LMS tasks", - description = "Whether to include LMS tasks in the list.", - position = 7 - ) - default boolean isLMSEnabled() - { - return true; - } - @ConfigSection( name = "!taskman Command", description = "Configuration options for the !taskman command", - position = 8 + position = 30 ) - String command = "command"; + String commandSection = "command"; @ConfigItem( keyName = IS_COMMAND_ENABLED_KEY, name = "Enable command", description = "When you or others type !taskman in the chat, it will be replaced by your current task status", - section = command, - position = 0 + section = commandSection, + position = 31 ) default boolean isCommandEnabled() { diff --git a/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java b/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java index da520c22..9d5447fe 100644 --- a/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java +++ b/src/main/java/com/collectionlogmaster/CollectionLogMasterPlugin.java @@ -1,10 +1,9 @@ package com.collectionlogmaster; -import com.collectionlogmaster.command.DevCommandsManager; import com.collectionlogmaster.command.TaskmanCommandManager; import com.collectionlogmaster.input.MouseManager; import com.collectionlogmaster.synchronization.clog.CollectionLogService; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.InterfaceManager; import com.collectionlogmaster.ui.TaskOverlay; import com.collectionlogmaster.util.GsonOverride; @@ -54,9 +53,6 @@ public class CollectionLogMasterPlugin extends Plugin { @Inject public TaskmanCommandManager taskmanCommand; - @Inject - public DevCommandsManager devCommands; - @Override protected void startUp() { CollectionLogMasterPlugin.staticInjector = getInjector(); @@ -67,7 +63,6 @@ protected void startUp() { pluginUpdateNotifier.startUp(); interfaceManager.startUp(); taskmanCommand.startUp(); - devCommands.startUp(); this.taskOverlay.setResizable(true); this.overlayManager.add(this.taskOverlay); } @@ -80,7 +75,6 @@ protected void shutDown() { pluginUpdateNotifier.shutDown(); interfaceManager.shutDown(); taskmanCommand.shutDown(); - devCommands.shutDown(); this.overlayManager.remove(this.taskOverlay); } diff --git a/src/main/java/com/collectionlogmaster/command/DevCommandsManager.java b/src/main/java/com/collectionlogmaster/command/DevCommandsManager.java deleted file mode 100644 index e0288e79..00000000 --- a/src/main/java/com/collectionlogmaster/command/DevCommandsManager.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.collectionlogmaster.command; - - -import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.task.SaveDataStorage; -import com.collectionlogmaster.task.TaskListStorage; -import com.collectionlogmaster.util.EventBusSubscriber; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.events.CommandExecuted; -import net.runelite.client.eventbus.Subscribe; - -@Slf4j -@Singleton -public class DevCommandsManager extends EventBusSubscriber { - private final String SET_ACTIVE_TASK_COMMAND = "set-active-task"; - - @Inject - private SaveDataStorage saveDataStorage; - - @Inject - private TaskListStorage taskListStorage; - - @Inject - @Named("developerMode") - private boolean isDeveloperMode; - - @Subscribe - public void onCommandExecuted(CommandExecuted e) { - if (!isDeveloperMode) return; - - String command = e.getCommand(); - String[] args = e.getArguments(); - - log.debug("Command executed: ::{} {}", command, args); - if (command.equals(SET_ACTIVE_TASK_COMMAND)) { - executeSecActiveTaskCommand(args); - } - } - - private void executeSecActiveTaskCommand(String[] args) { - if (args.length != 1) return; - String taskPrefix = args[0]; - - for (Task task : taskListStorage.get().all()) { - String taskId = task.getId(); - if (taskId.startsWith(taskPrefix)) { - log.debug("Setting active task to {}", taskId); - saveDataStorage.get().setActiveTaskId(taskId); - return; - } - } - - log.debug("Unable to find task with prefix {}", taskPrefix); - } -} diff --git a/src/main/java/com/collectionlogmaster/command/TaskmanCommandManager.java b/src/main/java/com/collectionlogmaster/command/TaskmanCommandManager.java index d59a1132..934be0b8 100644 --- a/src/main/java/com/collectionlogmaster/command/TaskmanCommandManager.java +++ b/src/main/java/com/collectionlogmaster/command/TaskmanCommandManager.java @@ -7,7 +7,7 @@ import com.collectionlogmaster.domain.TaskTier; import com.collectionlogmaster.domain.command.CommandRequest; import com.collectionlogmaster.domain.command.CommandResponse; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.util.EventBusSubscriber; import com.collectionlogmaster.util.HttpClient; import com.collectionlogmaster.util.SimpleDebouncer; @@ -112,7 +112,7 @@ private void executeCommand(ChatMessage chatMessage, String message) { } HttpUrl url = baseApiUrl.newBuilder().addPathSegment(senderName).build(); - httpClient.getHttpRequestAsync(url.toString(), CommandResponse.class) + httpClient.get(url, CommandResponse.class) .thenAccept(res -> clientThread.invokeLater(() -> replaceChatMessage(chatMessage, res)) ); @@ -145,7 +145,8 @@ public void updateServerImmediately() { float currentProgress = taskService.getProgress().get(currentTier) * 100; CommandRequest data = new CommandRequest(taskId, taskService.getCurrentTier().displayName, (int) currentProgress); - httpClient.putHttpRequestAsync(url.toString(), GSON.toJson(data), null); + + httpClient.put(url, data, null); } private void replaceChatMessage(ChatMessage chatMessage, CommandResponse res) { diff --git a/src/main/java/com/collectionlogmaster/domain/command/CommandRequest.java b/src/main/java/com/collectionlogmaster/domain/command/CommandRequest.java index 041923bd..c5f149a4 100644 --- a/src/main/java/com/collectionlogmaster/domain/command/CommandRequest.java +++ b/src/main/java/com/collectionlogmaster/domain/command/CommandRequest.java @@ -1,12 +1,15 @@ package com.collectionlogmaster.domain.command; +import com.google.gson.annotations.SerializedName; import lombok.Data; import lombok.RequiredArgsConstructor; @Data @RequiredArgsConstructor public class CommandRequest { + @SerializedName("taskId") private final String taskId; private final String tier; + @SerializedName("progressPercentage") private final int progressPercentage; } diff --git a/src/main/java/com/collectionlogmaster/domain/command/CommandResponse.java b/src/main/java/com/collectionlogmaster/domain/command/CommandResponse.java index 7d406b98..c6bbecfa 100644 --- a/src/main/java/com/collectionlogmaster/domain/command/CommandResponse.java +++ b/src/main/java/com/collectionlogmaster/domain/command/CommandResponse.java @@ -1,10 +1,12 @@ package com.collectionlogmaster.domain.command; +import com.google.gson.annotations.SerializedName; import lombok.Data; @Data public class CommandResponse { private CommandTask task; private String tier; + @SerializedName("progressPercentage") private int progressPercentage; } diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/BaseSaveData.java b/src/main/java/com/collectionlogmaster/domain/savedata/BaseSaveData.java deleted file mode 100644 index 486a2fa1..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/BaseSaveData.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.collectionlogmaster.domain.savedata; - -import lombok.Getter; -import lombok.ToString; - -import javax.annotation.Nullable; - -@ToString -@Getter -public class BaseSaveData { - protected @Nullable Integer version = null; -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/SaveData.java b/src/main/java/com/collectionlogmaster/domain/savedata/SaveData.java deleted file mode 100644 index 0043072e..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/SaveData.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.collectionlogmaster.domain.savedata; - -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import javax.annotation.Nullable; -import java.util.HashSet; -import java.util.Set; - -@Getter -@ToString -public class SaveData extends BaseSaveData { - public final static int VERSION = 3; - - public SaveData() { - this.version = VERSION; - } - - @Setter - private @Nullable String activeTaskId = null; - - private final Set completedTasks = new HashSet<>(); -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/SaveDataUpdater.java b/src/main/java/com/collectionlogmaster/domain/savedata/SaveDataUpdater.java deleted file mode 100644 index a016aa74..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/SaveDataUpdater.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.collectionlogmaster.domain.savedata; - -import com.google.gson.reflect.TypeToken; -import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.domain.TaskTier; -import com.collectionlogmaster.domain.savedata.v0.V0SaveData; -import com.collectionlogmaster.domain.savedata.v0.V0Task; -import com.collectionlogmaster.domain.savedata.v0.V0TaskPointer; -import com.collectionlogmaster.domain.savedata.v1.V1SaveData; -import com.collectionlogmaster.domain.savedata.v1.V1TaskPointer; -import com.collectionlogmaster.domain.savedata.v2.V2SaveData; -import com.collectionlogmaster.task.SaveDataStorage; -import com.collectionlogmaster.task.TaskService; -import com.collectionlogmaster.util.FileUtils; -import lombok.extern.slf4j.Slf4j; - -import javax.inject.Inject; -import javax.inject.Singleton; -import java.lang.reflect.Type; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import static com.collectionlogmaster.util.GsonOverride.GSON; - -@Singleton -@Slf4j -@SuppressWarnings("deprecation") -public class SaveDataUpdater { - @Inject - private SaveDataStorage saveDataStorage; - - @Inject - private TaskService taskService; - - public SaveData update(String json) { - BaseSaveData base = GSON.fromJson(json, BaseSaveData.class); - if (base == null) { - return new SaveData(); - } - - if (base.getVersion() == V0SaveData.VERSION) { - V0SaveData v0Save = GSON.fromJson(json, V0SaveData.class); - return update(update(update(v0Save))); - } - - if (base.getVersion() == V1SaveData.VERSION) { - V1SaveData v1Save = GSON.fromJson(json, V1SaveData.class); - return update(update(v1Save)); - } - - if (base.getVersion() == V2SaveData.VERSION) { - V2SaveData v2Save = GSON.fromJson(json, V2SaveData.class); - return update(v2Save); - } - - if (base.getVersion() == SaveData.VERSION) { - return GSON.fromJson(json, SaveData.class); - } - - log.warn("Could not figure out save data version for json {}", json); - return new SaveData(); - } - - private SaveData update(V2SaveData v2Save) { - saveDataStorage.saveBackup(v2Save); - SaveData newSave = new SaveData(); - - newSave.getCompletedTasks().addAll(v2Save.getCompletedTasks()); - - Task activeTask = v2Save.getActiveTask(); - if (activeTask != null) { - newSave.setActiveTaskId(activeTask.getId()); - } - - return newSave; - } - - private V2SaveData update(V1SaveData v1Save) { - saveDataStorage.saveBackup(v1Save); - V2SaveData newSave = new V2SaveData(); - - V1TaskPointer v1ActiveTaskPointer = v1Save.getActiveTaskPointer(); - if (v1ActiveTaskPointer != null) { - newSave.setActiveTask(v1ActiveTaskPointer.getTask()); - } - - Set newCompletedTasks = newSave.getCompletedTasks(); - Set v1CompletedTasks = v1Save.getProgress().entrySet().stream() - .flatMap(entry -> entry.getValue().stream()) - .collect(Collectors.toSet()); - - newCompletedTasks.addAll(v1CompletedTasks); - - return newSave; - } - - private V1SaveData update(V0SaveData v0Save) { - saveDataStorage.saveBackup(v0Save); - V1SaveData newSave = new V1SaveData(); - - Type mapType = new TypeToken>>() {}.getType(); - Map> v0MigrationData = - FileUtils.loadResource("domain/savedata/v0-migration.json", mapType);; - - Map> v0Progress = v0Save.getProgress(); - Map> newProgress = newSave.getProgress(); - - for (TaskTier tier : TaskTier.values()) { - Set v0TierData = v0Progress.get(tier); - Set newTierData = newProgress.get(tier); - Map tierMigrationData = v0MigrationData.get(tier); - - for (Integer v0TaskId : v0TierData) { - if (tierMigrationData.containsKey(v0TaskId)) { - newTierData.add(tierMigrationData.get(v0TaskId)); - } - } - } - - V0TaskPointer v0TaskPointer = v0Save.getActiveTaskPointer(); - if (v0TaskPointer != null) { - V0Task v0Task = v0TaskPointer.getTask(); - String newTaskId = v0MigrationData.get(v0TaskPointer.getTaskTier()).get(v0Task.getId()); - Task newTask = taskService.getTaskById(newTaskId); - - // if we can't find the task, don't set it to avoid problems - if (newTask != null) { - newSave.setActiveTaskPointer(new V1TaskPointer(v0TaskPointer.getTaskTier(), newTask)); - } - } - - return newSave; - } -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0SaveData.java b/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0SaveData.java deleted file mode 100644 index 5cf6f9df..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0SaveData.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.collectionlogmaster.domain.savedata.v0; - -import com.collectionlogmaster.domain.TaskTier; -import com.collectionlogmaster.domain.savedata.BaseSaveData; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -@Getter -@ToString -@Deprecated -public class V0SaveData extends BaseSaveData { - public final static Integer VERSION = null; - - public V0SaveData() { - this.progress = new HashMap<>(); - - for (TaskTier tier : TaskTier.values()) { - this.progress.put(tier, new HashSet<>()); - } - } - - private final Map> progress; - - @Setter - private V0TaskPointer activeTaskPointer; - - @Setter - private TaskTier selectedTier; -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0Task.java b/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0Task.java deleted file mode 100644 index bdd285ed..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0Task.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.collectionlogmaster.domain.savedata.v0; - -import lombok.Getter; - -@Getter -@Deprecated -public class V0Task { - private int id; - private String description; - private int itemID; -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0TaskPointer.java b/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0TaskPointer.java deleted file mode 100644 index dcc44f07..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/v0/V0TaskPointer.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.collectionlogmaster.domain.savedata.v0; - -import com.collectionlogmaster.domain.TaskTier; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@Deprecated -public class V0TaskPointer { - - private TaskTier taskTier; - private V0Task task; -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/v1/V1SaveData.java b/src/main/java/com/collectionlogmaster/domain/savedata/v1/V1SaveData.java deleted file mode 100644 index d1fbd251..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/v1/V1SaveData.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.collectionlogmaster.domain.savedata.v1; - -import com.collectionlogmaster.domain.TaskTier; -import com.collectionlogmaster.domain.savedata.BaseSaveData; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import javax.annotation.Nullable; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -@Getter -@ToString -@Deprecated -public class V1SaveData extends BaseSaveData { - public final static int VERSION = 1; - - public V1SaveData() { - this.version = VERSION; - this.progress = new HashMap<>(); - - for (TaskTier tier : TaskTier.values()) { - this.progress.put(tier, new HashSet<>()); - } - } - - private final Map> progress; - - @Setter - private @Nullable V1TaskPointer activeTaskPointer; -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/v1/V1TaskPointer.java b/src/main/java/com/collectionlogmaster/domain/savedata/v1/V1TaskPointer.java deleted file mode 100644 index 90e44a25..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/v1/V1TaskPointer.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.collectionlogmaster.domain.savedata.v1; - -import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.domain.TaskTier; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@AllArgsConstructor -@NoArgsConstructor -@Deprecated -public class V1TaskPointer { - private TaskTier taskTier; - private Task task; -} diff --git a/src/main/java/com/collectionlogmaster/domain/savedata/v2/V2SaveData.java b/src/main/java/com/collectionlogmaster/domain/savedata/v2/V2SaveData.java deleted file mode 100644 index 36e00195..00000000 --- a/src/main/java/com/collectionlogmaster/domain/savedata/v2/V2SaveData.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.collectionlogmaster.domain.savedata.v2; - -import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.domain.savedata.BaseSaveData; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import javax.annotation.Nullable; -import java.util.HashSet; -import java.util.Set; - -@Getter -@ToString -@Deprecated -public class V2SaveData extends BaseSaveData { - public final static int VERSION = 2; - - public V2SaveData() { - this.version = VERSION; - } - - private final Set completedTasks = new HashSet<>(); - - @Setter - private @Nullable Task activeTask = null; -} diff --git a/src/main/java/com/collectionlogmaster/synchronization/SyncService.java b/src/main/java/com/collectionlogmaster/synchronization/SyncService.java index c0394d84..0c0992bb 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/SyncService.java +++ b/src/main/java/com/collectionlogmaster/synchronization/SyncService.java @@ -1,18 +1,22 @@ package com.collectionlogmaster.synchronization; -import com.collectionlogmaster.CollectionLogMasterPlugin; import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.TaskTier; import com.collectionlogmaster.synchronization.clog.CollectionLogVerifier; import com.collectionlogmaster.synchronization.diary.AchievementDiaryVerifier; import com.collectionlogmaster.synchronization.skill.SkillVerifier; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskAppClient; +import com.collectionlogmaster.taskapp.TaskAppStateStorage; +import com.collectionlogmaster.taskapp.TaskService; +import java.util.ArrayList; +import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; +import net.runelite.client.callback.ClientThread; @Slf4j @Singleton @@ -21,13 +25,16 @@ public class SyncService { private Client client; @Inject - private CollectionLogMasterPlugin plugin; + private ClientThread clientThread; @Inject private TaskService taskService; @Inject - private SyncService syncService; + private TaskAppStateStorage taskAppStateStorage; + + @Inject + private TaskAppClient taskAppClient; @Inject private CollectionLogVerifier collectionLogVerifier; @@ -57,30 +64,31 @@ private Boolean verify(Task task) { } public void sync() { - int updatedCount = 0; - for (TaskTier tier : TaskTier.values()) { - for (Task task : taskService.getTierTasks(tier)) { - Boolean isVerified = syncService.verify(task); - if (isVerified == null) { - continue; - } - - boolean taskChanged = isVerified != taskService.isComplete(task.getId()); - if (!taskChanged) { - continue; - } - - taskService.toggleComplete(task.getId()); - - String newStatus = isVerified ? "complete" : "incomplete"; - String msg = String.format("%s tier task '%s' marked as %s", tier.displayName, task.getName(), newStatus); - client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", msg, ""); + taskAppClient.sync( + collectionLogVerifier.verificationData(), + achievementDiaryVerifier.verificationData(), + skillVerifier.verificationData() + ).thenAccept(res -> { + int updatedCount = res.getCompleted().size() + res.getUncompleted().size(); + List messages = new ArrayList<>(updatedCount); + + for (String taskId : res.getCompleted()) { + Task task = taskService.getTaskById(taskId); + messages.add(String.format("Task '%s' marked as complete", task.getName())); + } - updatedCount++; + for (String taskId : res.getUncompleted()) { + Task task = taskService.getTaskById(taskId); + messages.add(String.format("Task '%s' marked as incomplete", task.getName())); } - } - String msg = String.format("Task synchronization finalized; %d tasks updated", updatedCount); - client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", msg, null); + messages.add(String.format("Task synchronization finalized; %d tasks updated", updatedCount)); + + clientThread.invoke(() -> { + for (String msg : messages) { + client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", msg, null); + } + }); + }).thenAccept((v) -> taskAppStateStorage.fetch()); } } diff --git a/src/main/java/com/collectionlogmaster/synchronization/Verifier.java b/src/main/java/com/collectionlogmaster/synchronization/Verifier.java index e30f10f2..068595ba 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/Verifier.java +++ b/src/main/java/com/collectionlogmaster/synchronization/Verifier.java @@ -3,7 +3,8 @@ import com.collectionlogmaster.domain.Task; import lombok.NonNull; -public interface Verifier { +public interface Verifier { boolean supports(@NonNull Task task); boolean verify(@NonNull Task task); + T verificationData(); } diff --git a/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogService.java b/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogService.java index e0f5dc62..910ff985 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogService.java +++ b/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogService.java @@ -39,6 +39,10 @@ public void onGameStateChanged(GameStateChanged gameStateChanged) { } } + public Set getObtainedItems() { + return Set.copyOf(obtainedItems); + } + public boolean isItemObtained(int itemId) { return obtainedItems.contains(itemId); } diff --git a/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogVerifier.java b/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogVerifier.java index 49504b6d..802730a5 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogVerifier.java +++ b/src/main/java/com/collectionlogmaster/synchronization/clog/CollectionLogVerifier.java @@ -3,6 +3,7 @@ import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.verification.clog.CollectionLogVerification; import com.collectionlogmaster.synchronization.Verifier; +import java.util.Set; import lombok.NonNull; import javax.inject.Inject; @@ -10,7 +11,7 @@ import java.util.Arrays; @Singleton -public class CollectionLogVerifier implements Verifier { +public class CollectionLogVerifier implements Verifier> { @Inject private CollectionLogService collectionLogService; @@ -28,4 +29,8 @@ public boolean verify(@NonNull Task task) { return totalObtained >= verif.getCount(); } + + public Set verificationData() { + return collectionLogService.getObtainedItems(); + } } diff --git a/src/main/java/com/collectionlogmaster/synchronization/diary/AchievementDiaryVerifier.java b/src/main/java/com/collectionlogmaster/synchronization/diary/AchievementDiaryVerifier.java index c5e1ce08..931d8fc2 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/diary/AchievementDiaryVerifier.java +++ b/src/main/java/com/collectionlogmaster/synchronization/diary/AchievementDiaryVerifier.java @@ -5,13 +5,16 @@ import com.collectionlogmaster.domain.verification.diary.DiaryDifficulty; import com.collectionlogmaster.domain.verification.diary.DiaryRegion; import com.collectionlogmaster.synchronization.Verifier; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; import lombok.NonNull; import javax.inject.Inject; import javax.inject.Singleton; @Singleton -public class AchievementDiaryVerifier implements Verifier { +public class AchievementDiaryVerifier implements Verifier>> { @Inject private AchievementDiaryService achievementDiaryService; @@ -23,9 +26,25 @@ public boolean verify(@NonNull Task task) { assert task.getVerification() instanceof AchievementDiaryVerification; AchievementDiaryVerification verif = (AchievementDiaryVerification) task.getVerification(); - DiaryRegion diary = verif.getRegion(); + DiaryRegion region = verif.getRegion(); DiaryDifficulty difficulty = verif.getDifficulty(); - return achievementDiaryService.isComplete(diary, difficulty); + return achievementDiaryService.isComplete(region, difficulty); + } + + public Map> verificationData() { + return Arrays.stream(DiaryRegion.values()) + .collect(Collectors.toMap( + region -> region, + this::verificationData + )); + } + + private Map verificationData(DiaryRegion region) { + return Arrays.stream(DiaryDifficulty.values()) + .collect(Collectors.toMap( + difficulty -> difficulty, + difficulty -> achievementDiaryService.isComplete(region, difficulty) + )); } } diff --git a/src/main/java/com/collectionlogmaster/synchronization/skill/SkillVerifier.java b/src/main/java/com/collectionlogmaster/synchronization/skill/SkillVerifier.java index 2616c8f2..9b8de299 100644 --- a/src/main/java/com/collectionlogmaster/synchronization/skill/SkillVerifier.java +++ b/src/main/java/com/collectionlogmaster/synchronization/skill/SkillVerifier.java @@ -3,14 +3,19 @@ import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.verification.skill.SkillVerification; import com.collectionlogmaster.synchronization.Verifier; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import lombok.NonNull; import net.runelite.api.Client; import javax.inject.Inject; import javax.inject.Singleton; +import net.runelite.api.Skill; @Singleton -public class SkillVerifier implements Verifier { +public class SkillVerifier implements Verifier> { @Inject private Client client; @@ -29,4 +34,12 @@ public boolean verify(@NonNull Task task) { return totalAchieved >= verif.getCount(); } + + public Map verificationData() { + return Arrays.stream(Skill.values()) + .collect(Collectors.toMap( + skill -> skill, + skill -> client.getSkillExperience(skill) + )); + } } diff --git a/src/main/java/com/collectionlogmaster/task/SaveDataStorage.java b/src/main/java/com/collectionlogmaster/task/SaveDataStorage.java deleted file mode 100644 index ad812297..00000000 --- a/src/main/java/com/collectionlogmaster/task/SaveDataStorage.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.collectionlogmaster.task; - -import com.collectionlogmaster.CollectionLogMasterConfig; -import com.google.gson.JsonSyntaxException; -import com.collectionlogmaster.domain.savedata.BaseSaveData; -import com.collectionlogmaster.domain.savedata.SaveData; -import com.collectionlogmaster.domain.savedata.SaveDataUpdater; -import com.collectionlogmaster.util.EventBusSubscriber; -import com.collectionlogmaster.util.SimpleDebouncer; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameState; -import net.runelite.api.events.GameStateChanged; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; - -import javax.inject.Inject; -import javax.inject.Singleton; -import java.time.Instant; - -import static com.collectionlogmaster.CollectionLogMasterConfig.CONFIG_GROUP; -import static com.collectionlogmaster.util.GsonOverride.GSON; - -@Singleton -@Slf4j -public class SaveDataStorage extends EventBusSubscriber { - public static final String SAVE_DATA_KEY = "save-data"; - - public static final String SAVE_DATA_BACKUP_KEY_BASE = "save-data-bk"; - - @Inject - private ConfigManager configManager; - - @Inject - private SaveDataUpdater saveDataUpdater; - - @Inject - private SimpleDebouncer saveDebouncer; - - private SaveData data; - - @Override - public void startUp() { - super.startUp(); - load(); - } - - @Subscribe - public void onGameStateChanged(GameStateChanged e) { - GameState state = e.getGameState(); - switch (state) { - case LOGGED_IN: - load(); - break; - - case LOGIN_SCREEN: - saveImmediately(); - break; - } - } - - public SaveData get() { - return data; - } - - public void save() { - log.debug("Scheduling save; {}", Instant.now()); - saveDebouncer.debounce(this::saveImmediately); - } - - public void saveImmediately() { - log.debug("Saving; {}", Instant.now()); - String json = GSON.toJson(data); - configManager.setRSProfileConfiguration(CONFIG_GROUP, SAVE_DATA_KEY, json); - } - - public void saveBackup(BaseSaveData data) { - String json = GSON.toJson(data); - configManager.setRSProfileConfiguration( - CONFIG_GROUP, - SAVE_DATA_BACKUP_KEY_BASE + data.getVersion(), - json - ); - } - - private void load() { - importOldPluginSave(); - data = read(); - } - - private @NonNull SaveData read() { - String json = configManager.getRSProfileConfiguration(CONFIG_GROUP, SAVE_DATA_KEY); - if (json == null) { - return new SaveData(); - } - - try { - return saveDataUpdater.update(json); - } catch (JsonSyntaxException e) { - log.error("Unable to parse save data JSON", e); - } - - return new SaveData(); - } - - private void importOldPluginSave() { - Boolean alreadyImported = configManager.getRSProfileConfiguration( - CollectionLogMasterConfig.CONFIG_GROUP, - "oldPluginSaveImported", - Boolean.class - ); - - if (alreadyImported != null && alreadyImported) { - return; - } - - log.info("Importing old plugin save for profile {}", configManager.getRSProfileKey()); - String oldSave = configManager.getRSProfileConfiguration("log-master", SAVE_DATA_KEY); - log.info("Old save: {}", oldSave); - - if (oldSave != null) { - configManager.setRSProfileConfiguration(CONFIG_GROUP, SAVE_DATA_KEY, oldSave); - } - - configManager.setRSProfileConfiguration(CONFIG_GROUP, "oldPluginSaveImported", true); - } -} diff --git a/src/main/java/com/collectionlogmaster/task/TaskListStorage.java b/src/main/java/com/collectionlogmaster/task/TaskListStorage.java deleted file mode 100644 index a71a11ef..00000000 --- a/src/main/java/com/collectionlogmaster/task/TaskListStorage.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.collectionlogmaster.task; - -import com.collectionlogmaster.domain.TieredTaskList; -import com.collectionlogmaster.util.FileUtils; -import com.collectionlogmaster.util.HttpClient; -import java.util.concurrent.CompletableFuture; -import javax.inject.Inject; -import javax.inject.Singleton; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; - - -@Slf4j -@Singleton -public class TaskListStorage { - private static final String LOCAL_TASK_LIST_FILE = "task-list.json"; - - private static final String REMOTE_TASK_LIST_URL = "https://raw.githubusercontent.com/OSRS-Taskman/collection-log-master/refs/heads/main/src/main/resources/com/collectionlogmaster/task-list.json"; - - private final HttpClient httpClient; - - private @NonNull TieredTaskList taskList = new TieredTaskList(); - - @Inject - public TaskListStorage(HttpClient httpClient) { - this.httpClient = httpClient; - loadAsync(); - } - - public @NonNull TieredTaskList get() { - return taskList; - } - - private void loadAsync() { - fetchRemoteAsync() - .exceptionally(t -> fetchLocal()) - .thenAccept(taskList -> this.taskList = taskList); - } - - private @NonNull TieredTaskList fetchLocal() { - return FileUtils.loadResource(LOCAL_TASK_LIST_FILE, TieredTaskList.class); - } - - private CompletableFuture fetchRemoteAsync() { - return httpClient.getHttpRequestAsync(REMOTE_TASK_LIST_URL, TieredTaskList.class); - } -} diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java new file mode 100644 index 00000000..70c3babd --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppAuthInterceptor.java @@ -0,0 +1,72 @@ +package com.collectionlogmaster.taskapp; + +import com.collectionlogmaster.CollectionLogMasterConfig; +import com.collectionlogmaster.taskapp.response.LoginResponse; +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; +import org.jetbrains.annotations.NotNull; + +public class TaskAppAuthInterceptor implements Interceptor { + private static final Duration TOKEN_DURATION = Duration.ofHours(12); + + private final TaskAppClient taskAppClient; + + private final CollectionLogMasterConfig config; + + private volatile String jwtToken = null; + + private volatile Instant tokenExpiresAt = Instant.MIN; + + public TaskAppAuthInterceptor(TaskAppClient taskAppClient, CollectionLogMasterConfig config) { + this.taskAppClient = taskAppClient; + this.config = config; + } + + private boolean isTokenValid() { + return jwtToken != null + && tokenExpiresAt.isAfter(Instant.now()); + } + + public void invalidateToken() { + jwtToken = null; + tokenExpiresAt = Instant.MIN; + } + + private boolean requestSkipsAuth(Request req) { + String path = req.url().encodedPath(); + return path.endsWith("/login") + || path.endsWith("/task-list"); + } + + private void authenticate() { + // it's fine to block here because this executes in the same thread as the request + LoginResponse res = taskAppClient.login(config.username(), config.password()).join(); + jwtToken = res.getToken(); + tokenExpiresAt = Instant.now().plus(TOKEN_DURATION); + } + + @Override + public @NotNull Response intercept(@NotNull Chain chain) throws IOException { + Request originalRequest = chain.request(); + + if (requestSkipsAuth(originalRequest)) { + return chain.proceed(originalRequest); + } + + synchronized (this) { + if (!isTokenValid()) { + authenticate(); + } + } + + Request newRequest = originalRequest.newBuilder() + .header("Authorization", "Bearer " + jwtToken) + .build(); + + return chain.proceed(newRequest); + } +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java new file mode 100644 index 00000000..673ffb46 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppClient.java @@ -0,0 +1,116 @@ +package com.collectionlogmaster.taskapp; + +import com.collectionlogmaster.CollectionLogMasterConfig; +import com.collectionlogmaster.domain.verification.diary.DiaryDifficulty; +import com.collectionlogmaster.domain.verification.diary.DiaryRegion; +import com.collectionlogmaster.taskapp.request.SyncRequest; +import com.collectionlogmaster.taskapp.request.LoginRequest; +import com.collectionlogmaster.taskapp.request.UpdateTaskRequest; +import com.collectionlogmaster.taskapp.response.GenerateTaskResponse; +import com.collectionlogmaster.taskapp.response.LoginResponse; +import com.collectionlogmaster.taskapp.response.SyncResponse; +import com.collectionlogmaster.taskapp.response.TaskListResponse; +import com.collectionlogmaster.taskapp.response.UserProfileResponse; +import com.collectionlogmaster.util.HttpClient; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Skill; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import org.jetbrains.annotations.NotNull; + +@Slf4j +@Singleton +public class TaskAppClient extends HttpClient { + private static final HttpUrl DEVELOPMENT_BASE_API_URL = new HttpUrl.Builder() + .scheme("http") + .host("127.0.0.1") + .port(5001) + .addPathSegments("api/v2") + .build(); + + private static final HttpUrl PRODUCTION_BASE_API_URL = new HttpUrl.Builder() + .scheme("https") + .host("www.osrstaskapp.com") + .addPathSegments("api/v2") + .build(); + + @Inject + @Named("developerMode") + private boolean isDeveloperMode; + + private final TaskAppAuthInterceptor taskAppAuthInterceptor; + + @Inject + public TaskAppClient(OkHttpClient okHttpClient, CollectionLogMasterConfig config) { + super(okHttpClient); + + taskAppAuthInterceptor = new TaskAppAuthInterceptor(this, config); + this.okHttpClient = okHttpClient.newBuilder() + .addInterceptor(taskAppAuthInterceptor) + .build(); + } + + private @NotNull HttpUrl buildApiUrl(String... segments) { + HttpUrl baseApiUrl = isDeveloperMode ? DEVELOPMENT_BASE_API_URL : PRODUCTION_BASE_API_URL; + HttpUrl.Builder builder = baseApiUrl.newBuilder(); + + for (String segment : segments) { + builder.addPathSegments(segment); + } + + return builder.build(); + } + + public void invalidateToken() { + taskAppAuthInterceptor.invalidateToken(); + } + + public CompletableFuture login(String username, String password) { + HttpUrl url = buildApiUrl("login"); + LoginRequest data = new LoginRequest(username, password); + + return post(url, data, LoginResponse.class); + } + + public CompletableFuture getTaskList() { + HttpUrl url = buildApiUrl("task-list"); + + return get(url, TaskListResponse.class); + } + + public CompletableFuture getUserProfile() { + HttpUrl url = buildApiUrl("user/profile"); + + return get(url, UserProfileResponse.class); + } + + public CompletableFuture updateTask(String taskId, boolean completed) { + HttpUrl url = buildApiUrl("user/tasks", taskId); + UpdateTaskRequest data = new UpdateTaskRequest(completed); + + return patch(url, data, null); + } + + public CompletableFuture generateTask() { + HttpUrl url = buildApiUrl("user/generate-task"); + + return post(url, GenerateTaskResponse.class); + } + + public CompletableFuture sync( + Set collectionLog, + Map> diaries, + Map skills + ) { + HttpUrl url = buildApiUrl("user/sync"); + SyncRequest data = new SyncRequest(collectionLog, diaries, skills); + + return post(url, data, SyncResponse.class); + } +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java new file mode 100644 index 00000000..2077cad2 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppState.java @@ -0,0 +1,18 @@ +package com.collectionlogmaster.taskapp; + +import lombok.Data; +import java.util.Set; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +public class TaskAppState { + private final String activeTaskId; + private final boolean isOfficial; + private final boolean isLmsEnabled; + private final Set completedTasks; + + public TaskAppState() { + this(null, true, true, Set.of()); + } +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java b/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java new file mode 100644 index 00000000..68fbefd9 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskAppStateStorage.java @@ -0,0 +1,56 @@ +package com.collectionlogmaster.taskapp; + +import com.collectionlogmaster.command.TaskmanCommandManager; +import com.collectionlogmaster.taskapp.domain.CompletedTask; +import com.collectionlogmaster.util.EventBusSubscriber; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; + +@Singleton +@Slf4j +public class TaskAppStateStorage extends EventBusSubscriber { + @Inject + private TaskAppClient taskAppClient; + + @Inject + private TaskmanCommandManager taskmanCommandManager; + + private volatile TaskAppState state = new TaskAppState(); + + @Override + public void startUp() { + super.startUp(); + fetch(); + } + + public TaskAppState get() { + return state; + } + + public CompletableFuture fetch() { + return taskAppClient.getUserProfile() + .thenAccept(res -> { + Set completedTasks = res.getCompletedTasks().stream() + .map(CompletedTask::getId) + .collect(Collectors.toUnmodifiableSet()); + + TaskAppState newState = new TaskAppState( + res.getActiveTaskId(), + res.isOfficial(), + res.isLmsEnabled(), + completedTasks + ); + + if (state.equals(newState)) { + return; + } + + state = newState; + taskmanCommandManager.updateServer(); + }); + } +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java b/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java new file mode 100644 index 00000000..5c457776 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskListStorage.java @@ -0,0 +1,35 @@ +package com.collectionlogmaster.taskapp; + +import com.collectionlogmaster.domain.TieredTaskList; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +@Singleton +public class TaskListStorage { + private final TaskAppClient taskAppClient; + + private @NonNull TieredTaskList taskList = new TieredTaskList(); + + @Inject + public TaskListStorage(TaskAppClient taskAppClient) { + this.taskAppClient = taskAppClient; + fetch(); + } + + public @NonNull TieredTaskList get() { + return taskList; + } + + public void fetch() { + taskAppClient.getTaskList() + .thenAccept(taskList -> this.taskList = taskList) + .exceptionally(e -> { + log.error("Failed to load task list", e); + return null; + }); + } +} diff --git a/src/main/java/com/collectionlogmaster/task/TaskService.java b/src/main/java/com/collectionlogmaster/taskapp/TaskService.java similarity index 53% rename from src/main/java/com/collectionlogmaster/task/TaskService.java rename to src/main/java/com/collectionlogmaster/taskapp/TaskService.java index 8e0ed710..fbae1797 100644 --- a/src/main/java/com/collectionlogmaster/task/TaskService.java +++ b/src/main/java/com/collectionlogmaster/taskapp/TaskService.java @@ -1,21 +1,17 @@ -package com.collectionlogmaster.task; +package com.collectionlogmaster.taskapp; import com.collectionlogmaster.CollectionLogMasterConfig; -import com.collectionlogmaster.command.TaskmanCommandManager; import com.collectionlogmaster.domain.Tag; import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.TaskTier; -import com.collectionlogmaster.domain.savedata.SaveData; -import com.collectionlogmaster.domain.verification.clog.CollectionLogVerification; import com.collectionlogmaster.util.EventBusSubscriber; import java.util.Arrays; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Singleton; import lombok.NonNull; @@ -31,24 +27,24 @@ public class TaskService extends EventBusSubscriber { private CollectionLogMasterConfig config; @Inject - private SaveDataStorage saveDataStorage; + private TaskAppStateStorage taskAppStateStorage; @Inject private TaskListStorage taskListStorage; @Inject - private TaskmanCommandManager taskmanCommandManager; + private TaskAppClient taskAppClient; @Override public void startUp() { super.startUp(); - saveDataStorage.startUp(); + taskAppStateStorage.startUp(); } @Override public void shutDown() { super.shutDown(); - saveDataStorage.shutDown(); + taskAppStateStorage.shutDown(); } @Subscribe @@ -57,21 +53,19 @@ public void onConfigChanged(ConfigChanged e) { return; } - if (!e.getKey().equals(CollectionLogMasterConfig.IS_LMS_ENABLED_KEY)) { - return; - } - - if (!config.isLMSEnabled()) { - Task activeTask = getActiveTask(); - if (activeTask != null && activeTask.isLMS()) { - saveDataStorage.get().setActiveTaskId(null); - saveDataStorage.save(); - } + String configKey = e.getKey(); + if ( + configKey.equals(CollectionLogMasterConfig.TASK_APP_USERNAME) + || configKey.equals(CollectionLogMasterConfig.TASK_APP_PASSWORD) + ) { + taskAppClient.invalidateToken(); + taskAppStateStorage.fetch(); + taskListStorage.fetch(); } } public Task getActiveTask() { - String activeTaskId = saveDataStorage.get().getActiveTaskId(); + String activeTaskId = taskAppStateStorage.get().getActiveTaskId(); return activeTaskId == null ? null : getTaskById(activeTaskId); } @@ -110,7 +104,7 @@ public List getTierTasks(TaskTier tier) { public List getTierTasks(TaskTier tier, boolean skipLMSCheck) { List tierTasks = taskListStorage.get().getForTier(tier); - if (!skipLMSCheck && !config.isLMSEnabled()) { + if (!skipLMSCheck && !taskAppStateStorage.get().isLmsEnabled()) { return filterTag(tierTasks, Tag.LMS); } @@ -138,7 +132,7 @@ public List getVisibleTiers() { } public @NonNull Map getProgress() { - SaveData data = saveDataStorage.get(); + TaskAppState data = taskAppStateStorage.get(); Set completedTasks = data.getCompletedTasks(); Map completionPercentages = new HashMap<>(); @@ -158,97 +152,42 @@ public List getVisibleTiers() { return completionPercentages; } - public Task generate() { - SaveData data = saveDataStorage.get(); - - String activeTaskId = data.getActiveTaskId(); - if (activeTaskId != null) { - log.warn("Tried to generate task when previous one wasn't completed yet"); - return null; - } - - List incompleteTierTasks = getIncompleteTierTasks(); - if (incompleteTierTasks.isEmpty()) { - log.warn("No tasks left"); - return null; - } - - Task generatedTask = pickRandomTask(incompleteTierTasks); - log.debug("New task generated: {}", generatedTask); - - data.setActiveTaskId(generatedTask.getId()); - saveDataStorage.save(); - taskmanCommandManager.updateServer(); - - return generatedTask; + public CompletableFuture generate() { + return taskAppClient.generateTask() + .thenCompose((res) -> taskAppStateStorage.fetch()) + .thenApply((v) -> getActiveTask()); } - public void complete() { - Task activeTask = getActiveTask(); - if (activeTask == null) { - return; - } - - complete(activeTask.getId()); + public CompletableFuture complete() { + return complete(taskAppStateStorage.get().getActiveTaskId()); } - public void complete(String taskId) { - SaveData data = saveDataStorage.get(); - Set completedTasks = data.getCompletedTasks(); - completedTasks.add(taskId); - - if (taskId.equals(data.getActiveTaskId())) { - data.setActiveTaskId(null); - } - - saveDataStorage.save(); - taskmanCommandManager.updateServer(); + public CompletableFuture complete(String taskId) { + return taskAppClient.updateTask(taskId, true) + .thenCompose((res) -> taskAppStateStorage.fetch()); } - public void uncomplete(String taskId) { - Set completedTasks = saveDataStorage.get().getCompletedTasks(); - completedTasks.remove(taskId); - - saveDataStorage.save(); - taskmanCommandManager.updateServer(); + public CompletableFuture uncomplete(String taskId) { + return taskAppClient.updateTask(taskId, false) + .thenCompose((res) -> taskAppStateStorage.fetch()); } - public boolean toggleComplete(String taskId) { + public CompletableFuture toggleComplete(String taskId) { if (isComplete(taskId)) { - uncomplete(taskId); - return false; + return uncomplete(taskId) + .thenApply(v -> false); } else { - complete(taskId); - return true; + return complete(taskId) + .thenApply(v -> true); } } public boolean isComplete(String taskId) { - Set completedTasks = saveDataStorage.get().getCompletedTasks(); + Set completedTasks = taskAppStateStorage.get().getCompletedTasks(); return completedTasks.contains(taskId); } - private Task pickRandomTask(List tasks) { - int index = (int) Math.floor(Math.random() * tasks.size()); - Task pickedTask = tasks.get(index); - - if (!(pickedTask.getVerification() instanceof CollectionLogVerification)) { - return pickedTask; - } - - // get first of similarly named tasks - String taskName = pickedTask.getName(); - Stream similarTasks = tasks.stream() - .filter(t -> taskName.equals(t.getName())) - .filter(t -> t.getVerification() instanceof CollectionLogVerification); - - //noinspection DataFlowIssue - return similarTasks.min(Comparator.comparingInt( - t -> ((CollectionLogVerification) t.getVerification()).getCount() - )).orElse(pickedTask); - } - private List filterTag(List list, Tag tag) { return list.stream() .filter(t -> !t.getTags().contains(tag)) diff --git a/src/main/java/com/collectionlogmaster/taskapp/domain/CompletedTask.java b/src/main/java/com/collectionlogmaster/taskapp/domain/CompletedTask.java new file mode 100644 index 00000000..357d4844 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/domain/CompletedTask.java @@ -0,0 +1,14 @@ +package com.collectionlogmaster.taskapp.domain; + +import java.time.Instant; +import java.util.Set; +import lombok.Data; + +@Data +public class CompletedTask { + private final String id; + private final Set completedItemIds; + // TODO: handle the dumb way we serialize dates in taskapp +// private final Instant assignedDate; +// private final Instant completedDate; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/request/LoginRequest.java b/src/main/java/com/collectionlogmaster/taskapp/request/LoginRequest.java new file mode 100644 index 00000000..98e1f3a4 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/request/LoginRequest.java @@ -0,0 +1,9 @@ +package com.collectionlogmaster.taskapp.request; + +import lombok.Data; + +@Data +public class LoginRequest { + private final String username; + private final String password; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/request/SyncRequest.java b/src/main/java/com/collectionlogmaster/taskapp/request/SyncRequest.java new file mode 100644 index 00000000..afa7dbc4 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/request/SyncRequest.java @@ -0,0 +1,15 @@ +package com.collectionlogmaster.taskapp.request; + +import com.collectionlogmaster.domain.verification.diary.DiaryDifficulty; +import com.collectionlogmaster.domain.verification.diary.DiaryRegion; +import java.util.Map; +import java.util.Set; +import lombok.Data; +import net.runelite.api.Skill; + +@Data +public class SyncRequest { + private final Set collectionLog; + private final Map> diaries; + private final Map skills; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/request/UpdateTaskRequest.java b/src/main/java/com/collectionlogmaster/taskapp/request/UpdateTaskRequest.java new file mode 100644 index 00000000..76a91f3e --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/request/UpdateTaskRequest.java @@ -0,0 +1,8 @@ +package com.collectionlogmaster.taskapp.request; + +import lombok.Data; + +@Data +public class UpdateTaskRequest { + private final boolean completed; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/response/GenerateTaskResponse.java b/src/main/java/com/collectionlogmaster/taskapp/response/GenerateTaskResponse.java new file mode 100644 index 00000000..8bc6eca8 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/response/GenerateTaskResponse.java @@ -0,0 +1,8 @@ +package com.collectionlogmaster.taskapp.response; + +import lombok.Data; + +@Data +public class GenerateTaskResponse { + private String taskId; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/response/LoginResponse.java b/src/main/java/com/collectionlogmaster/taskapp/response/LoginResponse.java new file mode 100644 index 00000000..269d8ab0 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/response/LoginResponse.java @@ -0,0 +1,8 @@ +package com.collectionlogmaster.taskapp.response; + +import lombok.Data; + +@Data +public class LoginResponse { + private final String token; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/response/SyncResponse.java b/src/main/java/com/collectionlogmaster/taskapp/response/SyncResponse.java new file mode 100644 index 00000000..63c2845c --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/response/SyncResponse.java @@ -0,0 +1,10 @@ +package com.collectionlogmaster.taskapp.response; + +import java.util.Set; +import lombok.Data; + +@Data +public class SyncResponse { + private final Set completed; + private final Set uncompleted; +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/response/TaskListResponse.java b/src/main/java/com/collectionlogmaster/taskapp/response/TaskListResponse.java new file mode 100644 index 00000000..cd23333b --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/response/TaskListResponse.java @@ -0,0 +1,11 @@ +package com.collectionlogmaster.taskapp.response; + +import com.collectionlogmaster.domain.TieredTaskList; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class TaskListResponse extends TieredTaskList { + +} diff --git a/src/main/java/com/collectionlogmaster/taskapp/response/UserProfileResponse.java b/src/main/java/com/collectionlogmaster/taskapp/response/UserProfileResponse.java new file mode 100644 index 00000000..6d60ace1 --- /dev/null +++ b/src/main/java/com/collectionlogmaster/taskapp/response/UserProfileResponse.java @@ -0,0 +1,14 @@ +package com.collectionlogmaster.taskapp.response; + +import com.collectionlogmaster.taskapp.domain.CompletedTask; +import java.util.List; +import lombok.Data; + +@Data +public class UserProfileResponse { + private final String username; + private final boolean isOfficial; + private final boolean isLmsEnabled; + private final String activeTaskId; + private final List completedTasks; +} diff --git a/src/main/java/com/collectionlogmaster/ui/TaskOverlay.java b/src/main/java/com/collectionlogmaster/ui/TaskOverlay.java index 6266b5a4..f0537016 100644 --- a/src/main/java/com/collectionlogmaster/ui/TaskOverlay.java +++ b/src/main/java/com/collectionlogmaster/ui/TaskOverlay.java @@ -2,7 +2,7 @@ import com.collectionlogmaster.CollectionLogMasterConfig; import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.state.StateStore; import java.awt.Color; import java.awt.Dimension; diff --git a/src/main/java/com/collectionlogmaster/ui/component/MainTabbedContainer.java b/src/main/java/com/collectionlogmaster/ui/component/MainTabbedContainer.java index 5e085d29..3d3461c3 100644 --- a/src/main/java/com/collectionlogmaster/ui/component/MainTabbedContainer.java +++ b/src/main/java/com/collectionlogmaster/ui/component/MainTabbedContainer.java @@ -2,7 +2,7 @@ import com.collectionlogmaster.CollectionLogMasterPlugin; import com.collectionlogmaster.domain.TaskTier; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.generic.ScrollAxis; import com.collectionlogmaster.ui.generic.UIComponent; import com.collectionlogmaster.ui.generic.UIScrollableContainer; diff --git a/src/main/java/com/collectionlogmaster/ui/component/TaskComponent.java b/src/main/java/com/collectionlogmaster/ui/component/TaskComponent.java index 3544fae4..a6ea48a5 100644 --- a/src/main/java/com/collectionlogmaster/ui/component/TaskComponent.java +++ b/src/main/java/com/collectionlogmaster/ui/component/TaskComponent.java @@ -2,7 +2,7 @@ import com.collectionlogmaster.CollectionLogMasterPlugin; import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.InterfaceManager; import com.collectionlogmaster.ui.generic.BorderTheme; import com.collectionlogmaster.ui.generic.UIBorderedContainer; @@ -21,6 +21,7 @@ import net.runelite.api.widgets.WidgetSizeMode; import net.runelite.api.widgets.WidgetTextAlignment; import net.runelite.api.widgets.WidgetType; +import net.runelite.client.callback.ClientThread; import org.jetbrains.annotations.Range; @Accessors(chain = true) @@ -40,6 +41,9 @@ public class TaskComponent extends UIComponent { @Inject private InterfaceManager interfaceManager; + @Inject + private ClientThread clientThread; + @Inject private TaskService taskService; @@ -79,11 +83,12 @@ private void onActionSelected(ScriptEvent e) { return; case 2: - boolean isComplete = taskService.toggleComplete(task.getId()); - setOpacity(isComplete ? 0 : 175) - .setTheme(isComplete ? BorderTheme.ETCHED_GREEN_DYED : BorderTheme.ETCHED) - .revalidate(); - return; + taskService.toggleComplete(task.getId()).thenAccept( + (isComplete) -> clientThread.invoke( + () -> setOpacity(isComplete ? 0 : 175) + .setTheme(isComplete ? BorderTheme.ETCHED_GREEN_DYED : BorderTheme.ETCHED) + .revalidate() + )); } } diff --git a/src/main/java/com/collectionlogmaster/ui/component/TaskDashboard.java b/src/main/java/com/collectionlogmaster/ui/component/TaskDashboard.java index 7f68fe13..2f72c17f 100644 --- a/src/main/java/com/collectionlogmaster/ui/component/TaskDashboard.java +++ b/src/main/java/com/collectionlogmaster/ui/component/TaskDashboard.java @@ -5,7 +5,7 @@ import com.collectionlogmaster.domain.Task; import com.collectionlogmaster.domain.TaskTier; import com.collectionlogmaster.synchronization.SyncService; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.generic.UIComponent; import com.collectionlogmaster.ui.generic.UIUtil; import com.collectionlogmaster.ui.generic.button.UIButton.State; @@ -160,28 +160,30 @@ private void initializeWidgets() { } private void generateTask() { - Task generatedTask = taskService.generate(); - List rollTasks = getRollTasks(); + taskService.generate() + .thenAccept(generatedTask -> { + List rollTasks = getRollTasks(); - Stack> stepStack = new Stack<>(); - stepStack.push(Pair.of(generatedTask, 0)); + Stack> stepStack = new Stack<>(); + stepStack.push(Pair.of(generatedTask, 0)); - int timeLeft = config.rollTime(); - while (timeLeft > 0) { - int stepDelay = calculateStepDelay(stepStack.size() - 1); + int timeLeft = config.rollTime(); + while (timeLeft > 0) { + int stepDelay = calculateStepDelay(stepStack.size() - 1); - stepStack.push(Pair.of( - rollTasks.get(stepStack.size()), - stepDelay - )); + stepStack.push(Pair.of( + rollTasks.get(stepStack.size()), + stepDelay + )); - timeLeft -= stepDelay; - } + timeLeft -= stepDelay; + } - executeRollStep(stepStack); + executeRollStep(stepStack); - generateButton.setState(State.DISABLED) - .revalidate(); + generateButton.setState(State.DISABLED) + .revalidate(); + }); } /** @@ -250,10 +252,10 @@ public void revalidate() { if (activeTask != null) { completeButton.setState(State.DEFAULT) .setName(UIUtil.formatName(activeTask.getName())) - .setAction("Complete", () -> { - taskService.complete(); - revalidate(); - }) + .setAction("Complete", () -> + taskService.complete() + .thenRun(() -> clientThread.invoke(this::revalidate)) + ) .revalidate(); generateButton.setState(State.DISABLED) diff --git a/src/main/java/com/collectionlogmaster/ui/component/TaskInfo.java b/src/main/java/com/collectionlogmaster/ui/component/TaskInfo.java index 7f41e235..a9607c77 100644 --- a/src/main/java/com/collectionlogmaster/ui/component/TaskInfo.java +++ b/src/main/java/com/collectionlogmaster/ui/component/TaskInfo.java @@ -10,7 +10,7 @@ import com.collectionlogmaster.domain.verification.skill.SkillVerification; import com.collectionlogmaster.synchronization.clog.CollectionLogService; import com.collectionlogmaster.synchronization.diary.AchievementDiaryService; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.generic.UIComponent; import com.collectionlogmaster.ui.generic.UIGridContainer; import com.collectionlogmaster.ui.generic.UIProgressBar; @@ -34,6 +34,7 @@ import net.runelite.api.widgets.WidgetSizeMode; import net.runelite.api.widgets.WidgetTextAlignment; import net.runelite.api.widgets.WidgetType; +import net.runelite.client.callback.ClientThread; import net.runelite.client.game.ItemManager; import net.runelite.client.util.LinkBrowser; import org.apache.commons.lang3.tuple.Pair; @@ -63,6 +64,9 @@ public class TaskInfo extends UIComponent { @Inject private ItemManager itemManager; + @Inject + private ClientThread clientThread; + @Inject private TaskService taskService; @@ -287,8 +291,8 @@ private void initializeWidgets() { .setFont(FontID.BOLD_12) .setText("Mark Complete") .setAction("Mark", () -> { - taskService.toggleComplete(task.getId()); - revalidate(); + taskService.toggleComplete(task.getId()) + .thenRun(() -> clientThread.invoke(this::revalidate)); }) .revalidate(); } diff --git a/src/main/java/com/collectionlogmaster/ui/component/TaskList.java b/src/main/java/com/collectionlogmaster/ui/component/TaskList.java index ff8250cf..34695489 100644 --- a/src/main/java/com/collectionlogmaster/ui/component/TaskList.java +++ b/src/main/java/com/collectionlogmaster/ui/component/TaskList.java @@ -2,7 +2,7 @@ import com.collectionlogmaster.CollectionLogMasterPlugin; import com.collectionlogmaster.domain.Task; -import com.collectionlogmaster.task.TaskService; +import com.collectionlogmaster.taskapp.TaskService; import com.collectionlogmaster.ui.generic.BorderTheme; import com.collectionlogmaster.ui.generic.UIComponent; import com.collectionlogmaster.ui.generic.UIGridContainer; diff --git a/src/main/java/com/collectionlogmaster/util/GsonOverride.java b/src/main/java/com/collectionlogmaster/util/GsonOverride.java index d4d44e5c..d5c856c2 100644 --- a/src/main/java/com/collectionlogmaster/util/GsonOverride.java +++ b/src/main/java/com/collectionlogmaster/util/GsonOverride.java @@ -1,5 +1,6 @@ package com.collectionlogmaster.util; +import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.collectionlogmaster.domain.Tag; @@ -22,6 +23,8 @@ public class GsonOverride { @Inject public GsonOverride(Gson originalGson) { GsonBuilder gsonBuilder = originalGson.newBuilder() + .enableComplexMapKeySerialization() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .registerTypeAdapter(Verification.class, new VerificationAdapter()) .registerTypeAdapter(VerificationMethod.class, new EnumAdapter<>(VerificationMethod.class)) .registerTypeAdapter(DiaryRegion.class, new EnumAdapter<>(DiaryRegion.class)) diff --git a/src/main/java/com/collectionlogmaster/util/HttpClient.java b/src/main/java/com/collectionlogmaster/util/HttpClient.java index e4f5c95f..20c8913d 100644 --- a/src/main/java/com/collectionlogmaster/util/HttpClient.java +++ b/src/main/java/com/collectionlogmaster/util/HttpClient.java @@ -1,6 +1,7 @@ package com.collectionlogmaster.util; import com.collectionlogmaster.PluginUpdateNotifier; +import com.google.gson.JsonObject; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import net.runelite.client.RuneLiteProperties; @@ -9,7 +10,6 @@ import javax.annotation.Nullable; import javax.inject.Inject; import java.io.IOException; -import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; @@ -17,37 +17,40 @@ @Slf4j public class HttpClient { - private static final MediaType JSON_MEDIA_TYPE = Objects.requireNonNull(MediaType.parse("application/json; charset=utf-8")); + private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8"); - @Inject - private OkHttpClient okHttpClient; + protected OkHttpClient okHttpClient; private final String userAgent; @Inject - public HttpClient() { + public HttpClient(OkHttpClient okHttpClient) { + this.okHttpClient = okHttpClient; + String runeliteVersion = RuneLiteProperties.getVersion(); String pluginVersion = PluginUpdateNotifier.getPluginVersion(); - userAgent = "RuneLite:" + runeliteVersion + "," + "CLogMaster:" + pluginVersion; + userAgent = "RuneLite: " + runeliteVersion + ", CLogMaster: " + pluginVersion; } - private Request.Builder buildRequest(String url, Consumer methodSetter) { + protected Request.Builder buildRequest(HttpUrl url, Consumer methodSetter) { Request.Builder builder = new Request.Builder() .url(url) .header("Content-Type", "application/json") .header("User-Agent", userAgent); + methodSetter.accept(builder); + return builder; } - private CompletableFuture executeHttpRequestAsync(Request request) { + protected CompletableFuture executeRequest(Request request) { log.debug("Sending {} request to {}; data = {}", request.method(), request.url(), request.body()); CompletableFuture future = new CompletableFuture<>(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { - log.warn("Async request failed.", e); + log.error("Async request failed.", e); future.completeExceptionally(e); } @@ -59,28 +62,42 @@ public void onResponse(@NonNull Call call, @NonNull Response response) { return future; } - public CompletableFuture postHttpRequestAsync(String url, String data, @Nullable Class clazz) { - RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, data); + public CompletableFuture get(HttpUrl url, @Nullable Class clazz) { + Request request = buildRequest(url, Request.Builder::get).build(); + + return executeRequest(request) + .thenApply((response) -> parseResponse(response, clazz)); + } + + public CompletableFuture post(HttpUrl url, @Nullable Class clazz) { + return post(url, new JsonObject(), clazz); + } + + public CompletableFuture post(HttpUrl url, Object data, @Nullable Class clazz) { + RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, GSON.toJson(data)); Request request = buildRequest(url, builder -> builder.post(body)).build(); - return executeHttpRequestAsync(request) - .thenApply((response) -> handleResponse(response, clazz)); + + return executeRequest(request) + .thenApply((response) -> parseResponse(response, clazz)); } - public CompletableFuture putHttpRequestAsync(String url, String data, @Nullable Class clazz) { - RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, data); + public CompletableFuture put(HttpUrl url, Object data, @Nullable Class clazz) { + RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, GSON.toJson(data)); Request request = buildRequest(url, builder -> builder.put(body)).build(); - return executeHttpRequestAsync(request) - .thenApply((response) -> handleResponse(response, clazz)); + + return executeRequest(request) + .thenApply((response) -> parseResponse(response, clazz)); } - public CompletableFuture getHttpRequestAsync(String url, @Nullable Class clazz) { - Request request = buildRequest(url, Request.Builder::get) - .build(); - return executeHttpRequestAsync(request) - .thenApply((response) -> handleResponse(response, clazz)); + public CompletableFuture patch(HttpUrl url, Object data, @Nullable Class clazz) { + RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, GSON.toJson(data)); + Request request = buildRequest(url, builder -> builder.patch(body)).build(); + + return executeRequest(request) + .thenApply((response) -> parseResponse(response, clazz)); } - private T handleResponse(Response response, @Nullable Class clazz) { + protected T parseResponse(Response response, @Nullable Class clazz) { try (Response res = response) { ResponseBody body = res.body(); @@ -99,7 +116,8 @@ private T handleResponse(Response response, @Nullable Class clazz) { return GSON.fromJson(bodyString, clazz); } catch (IOException e) { - throw new RuntimeException("Error reading response body"); + log.error("Failed to parse response body.", e); + throw new RuntimeException("Failed to parse response body", e); } } } diff --git a/src/main/java/com/collectionlogmaster/util/SimpleDebouncer.java b/src/main/java/com/collectionlogmaster/util/SimpleDebouncer.java index 73baa954..578ead15 100644 --- a/src/main/java/com/collectionlogmaster/util/SimpleDebouncer.java +++ b/src/main/java/com/collectionlogmaster/util/SimpleDebouncer.java @@ -5,25 +5,25 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import lombok.Setter; +import lombok.experimental.Accessors; public class SimpleDebouncer { - public interface Callback { - void call(); - } - - Future future; + private Future future; @Inject private ScheduledExecutorService executorService; - private final static int MS_DELAY = 500; + @Setter + @Accessors(chain = true) + private int delay = 500; - public synchronized void debounce(Callback cb) { + public synchronized void debounce(Runnable cb) { if (future != null) { future.cancel(false); future = null; } - future = executorService.schedule(cb::call, MS_DELAY, TimeUnit.MILLISECONDS); + future = executorService.schedule(cb, delay, TimeUnit.MILLISECONDS); } } diff --git a/src/main/resources/com/collectionlogmaster/ui/transparent.png b/src/main/resources/com/collectionlogmaster/ui/sprites/transparent.png similarity index 100% rename from src/main/resources/com/collectionlogmaster/ui/transparent.png rename to src/main/resources/com/collectionlogmaster/ui/sprites/transparent.png