diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java index 9c821ad09b..584da6640d 100644 --- a/src/main/java/seedu/duke/Duke.java +++ b/src/main/java/seedu/duke/Duke.java @@ -10,6 +10,7 @@ import seedu.duke.data.meal.Meal; import seedu.duke.parser.Parser; import seedu.duke.exerciselog.Log; +import seedu.duke.storagefile.GoalStorage; import seedu.duke.ui.TextUi; import seedu.duke.storagefile.StorageFile; @@ -27,10 +28,12 @@ public class Duke { public static GoalList achievedGoals = new GoalList(); public static Log exerciseLog = new Log(); public static StorageFile exerciseLogStorage; + public static GoalStorage goalStorage; static ArrayList meals = new ArrayList(); private TextUi ui; private String dirPath = "data"; private String exerciseLogFilePath = "./data/ExerciseLog.txt"; + private String goalFilePath = "./data/GoalRecord.txt"; public static void main(String... launchArgs) { new Duke().run(launchArgs); } @@ -55,7 +58,9 @@ private void start(String[] launchArgs) { try { this.ui = new TextUi(); this.exerciseLogStorage = StorageFile.initializeStorage(dirPath, exerciseLogFilePath); - exerciseLogStorage.checkForTextFile(exerciseLog); + exerciseLogStorage.checkForLogTextFile(exerciseLog); + this.goalStorage = GoalStorage.initializeGoalStorage(dirPath, goalFilePath); + goalStorage.restoreGoalRecord(); ui.showWelcomeMessage(VERSION, "storage.getPath()"); MealCommand.setMeals(meals); } catch (Exception e) { // TODO: change to specific storage exceptions later diff --git a/src/main/java/seedu/duke/commands/HelpCommand.java b/src/main/java/seedu/duke/commands/HelpCommand.java index 17596b3bd9..cc1160b134 100644 --- a/src/main/java/seedu/duke/commands/HelpCommand.java +++ b/src/main/java/seedu/duke/commands/HelpCommand.java @@ -21,20 +21,18 @@ public class HelpCommand extends Command { @Override public CommandResult execute() { - String HelpMsg = HelpCommand.MESSAGE_USAGE + "\n"; - HelpMsg += LogCommand.MESSAGE_USAGE + "\n"; - HelpMsg += DeleteLogCommand.MESSAGE_USAGE + "\n"; - HelpMsg += UpdateLogCommand.MESSAGE_USAGE + "\n"; - HelpMsg += ViewLogCommand.MESSAGE_USAGE + "\n"; - HelpMsg += GoalCommand.MESSAGE_USAGE + "\n"; - HelpMsg += GoalCommand.MESSAGE_USAGE + "\n"; - HelpMsg += DeleteLogCommand.MESSAGE_USAGE + "\n"; - HelpMsg += ViewGoalCommand.MESSAGE_USAGE + "\n"; - HelpMsg += AchieveGoalCommand.MESSAGE_USAGE + "\n"; - HelpMsg += AchievementCommand.MESSAGE_USAGE; return new CommandResult( - HelpMsg + HelpCommand.MESSAGE_USAGE + + " \n" + LogCommand.MESSAGE_USAGE + + "\n" + DeleteLogCommand.MESSAGE_USAGE + + "\n" + UpdateLogCommand.MESSAGE_USAGE + + "\n" + ViewLogCommand.MESSAGE_USAGE + + "\n" + GoalCommand.MESSAGE_USAGE + + "\n" + DeleteLogCommand.MESSAGE_USAGE + + "\n" + ViewGoalCommand.MESSAGE_USAGE + + "\n" + AchieveGoalCommand.MESSAGE_USAGE + + "\n" + AchievementCommand.MESSAGE_USAGE // + "\n" + DeleteCommand.MESSAGE_USAGE // + "\n" + ClearCommand.MESSAGE_USAGE diff --git a/src/main/java/seedu/duke/commands/goal/DeleteGoalCommand.java b/src/main/java/seedu/duke/commands/goal/DeleteGoalCommand.java index 79505fd021..9e2c36cd48 100644 --- a/src/main/java/seedu/duke/commands/goal/DeleteGoalCommand.java +++ b/src/main/java/seedu/duke/commands/goal/DeleteGoalCommand.java @@ -5,6 +5,8 @@ import seedu.duke.data.GoalList; import seedu.duke.data.exception.IncorrectFormatException; +import java.io.IOException; + public class DeleteGoalCommand extends Command { public static final String COMMAND_WORD = "deleteg"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Delete an goal from the current goal list.\n" @@ -29,10 +31,13 @@ public CommandResult execute() { feedbackToUser = "Please input a valid number for delete index."; } catch (IncorrectFormatException ife) { feedbackToUser = ife.getMessage(); + } catch (IOException io) { + feedbackToUser = "Failed to save data. Please check the output file and restart the app."; } catch (Exception e) { feedbackToUser = "Something went wrong, please try again."; } + return new CommandResult(feedbackToUser); } diff --git a/src/main/java/seedu/duke/commands/goal/GoalCommand.java b/src/main/java/seedu/duke/commands/goal/GoalCommand.java index 4866eec74c..a774cba70d 100644 --- a/src/main/java/seedu/duke/commands/goal/GoalCommand.java +++ b/src/main/java/seedu/duke/commands/goal/GoalCommand.java @@ -6,6 +6,8 @@ import seedu.duke.data.exception.IncorrectFormatException; import seedu.duke.data.exception.InvalidDateException; +import java.io.IOException; + public class GoalCommand extends Command { @@ -35,10 +37,15 @@ public CommandResult execute() { feedbackToUser = "Please input a valid number for calories."; } catch (InvalidDateException ide) { feedbackToUser = ide.getMessage(); + } catch (IOException io) { + feedbackToUser = "Failed to save data. Please check the output file and restart the app."; } catch (Exception e) { feedbackToUser = "Something went wrong, please try again."; } + + + return new CommandResult(feedbackToUser); } diff --git a/src/main/java/seedu/duke/commands/logcommands/UpdateLogCommand.java b/src/main/java/seedu/duke/commands/logcommands/UpdateLogCommand.java index 4173bac704..84ff4694d0 100644 --- a/src/main/java/seedu/duke/commands/logcommands/UpdateLogCommand.java +++ b/src/main/java/seedu/duke/commands/logcommands/UpdateLogCommand.java @@ -85,7 +85,8 @@ public CommandResult execute() throws IncorrectFormatException, IOException { feedbackToUser = Duke.exerciseLog.updateExercise(month, day, oldExerciseName.trim(), oldCaloriesBurned, newExerciseName.trim(), newCaloriesBurned) ? "Exercise successfully updated!" : "Could not find exercise to update."; - Duke.exerciseLogStorage.updateExerciseInFile(month, day, oldExerciseName.trim().split(" "), oldCaloriesBurned, + Duke.exerciseLogStorage.updateExerciseInFile(month, day, + oldExerciseName.trim().split(" "), oldCaloriesBurned, newExerciseName.trim().split(" "), newCaloriesBurned); return new CommandResult(feedbackToUser); diff --git a/src/main/java/seedu/duke/data/Date.java b/src/main/java/seedu/duke/data/Date.java index d84531c278..647c3b5c27 100644 --- a/src/main/java/seedu/duke/data/Date.java +++ b/src/main/java/seedu/duke/data/Date.java @@ -20,31 +20,19 @@ public class Date { public String standardString; transient LocalDate date; - // @SerializedName("rawData") - /** - * Create a new date. - * - * @param rawData A String that needs to comply with a supported format and - * indicates a correct date that will be recorded by this Date - * instance. - * @throws TipsException Any excption will be throw in this type, which contains - * information about this exception and the possible - * solution. + * @param rawData refers to the date String + * @throws InvalidDateException if failed to parse date string input */ public Date(String rawData) throws InvalidDateException { setRawData(rawData); } /** - * Modifying an existing date with a rawData String. - * - * @param rawData A String that needs to comply with a supported format and - * indicates a correct date that will be recorded by this Date - * instance. - * @throws TipsException Any excption will be throw in this type, which contains - * information about this exception and the possible - * solution. + * The method is used to set up the date field of a Date object + * It contains the actual implementation to parse date information from a string + * @param rawData refers to a date string + * @throws InvalidDateException if failed to parse date string input */ public void setRawData(String rawData) throws InvalidDateException { for (DateTimeFormatter formatter : formatters) { diff --git a/src/main/java/seedu/duke/data/Goal.java b/src/main/java/seedu/duke/data/Goal.java index c0cbdb508a..d067adeae3 100644 --- a/src/main/java/seedu/duke/data/Goal.java +++ b/src/main/java/seedu/duke/data/Goal.java @@ -1,6 +1,5 @@ package seedu.duke.data; -import seedu.duke.data.Date; import seedu.duke.data.exception.InvalidDateException; diff --git a/src/main/java/seedu/duke/data/GoalList.java b/src/main/java/seedu/duke/data/GoalList.java index e396313267..3d9cc67403 100644 --- a/src/main/java/seedu/duke/data/GoalList.java +++ b/src/main/java/seedu/duke/data/GoalList.java @@ -5,8 +5,8 @@ import seedu.duke.data.exception.IncorrectFormatException; import seedu.duke.data.exception.InvalidDateException; import seedu.duke.ui.TextUi; -import seedu.duke.data.DateTime; +import java.io.IOException; import java.util.ArrayList; public class GoalList extends ArrayList { @@ -39,12 +39,15 @@ public int getGoalCount() { * @param cmd Raw user Command * @return message of succeeding to delete goal and tell user the updated total number of goals */ - public static String deleteGoal(String cmd) throws IncorrectFormatException, NumberFormatException { + public static String deleteGoal(String cmd) throws IncorrectFormatException, + NumberFormatException, IOException { verifyDeleteGoalInput(cmd); String[] cmdSplit = cmd.toLowerCase().trim().split(" "); int index = Integer.parseInt(cmdSplit[1]); Goal targetGoal = Duke.goalList.goals.remove(index - 1); Duke.goalList.goalCount--; + Duke.goalStorage.overwriteGoalToFile(); + return TextUi.deleteGoalMsg(targetGoal) + TextUi.noOfGoalMsg(Duke.goalList.goalCount); } @@ -142,7 +145,7 @@ private static void verifyAchieveGoalInput(String cmd) throws IncorrectFormatExc * @throws NumberFormatException if the user does not input a valid number */ public static String addGoal(String userCmd) throws IncorrectFormatException, NumberFormatException, - InvalidDateException { + InvalidDateException, IOException { verifyGoalInput(userCmd); //if invalid, exceptions is thrown String[] cmdSplit = userCmd.split(" "); @@ -151,6 +154,7 @@ public static String addGoal(String userCmd) throws IncorrectFormatException, Nu Duke.goalList.goals.add(new Goal(calories, date)); Duke.goalList.goalCount++; + Duke.goalStorage.overwriteGoalToFile(); return TextUi.addGoalSuccessMessage(); } diff --git a/src/main/java/seedu/duke/storagefile/GoalStorage.java b/src/main/java/seedu/duke/storagefile/GoalStorage.java new file mode 100644 index 0000000000..4d60d534cc --- /dev/null +++ b/src/main/java/seedu/duke/storagefile/GoalStorage.java @@ -0,0 +1,107 @@ +package seedu.duke.storagefile; + +import seedu.duke.Duke; +import seedu.duke.data.GoalList; +import seedu.duke.ui.TextUi; + +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Scanner; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +public class GoalStorage extends StorageFile { + + public GoalStorage(String dirName, String textFileName) { + super(dirName, textFileName); + } + + public static GoalStorage initializeGoalStorage(String dirName, String textFilePath) { + return new GoalStorage(dirName, textFilePath); + } + + /** + * This method restore any saved goal into goal list at the beginning to start the app. + * Both directory or save file not found will cause the app to start with an empty goal list + * if error occurs in loading saved date, the app will start with empty list immediately + * without loading part of data + * Note that this method also set up the file writer into overwrite mode + */ + public void restoreGoalRecord() throws IOException { + if (dir.exists() && textFile.exists()) { + try { + Scanner s = new Scanner(textFile); + while (s.hasNextLine()) { + String line = s.nextLine(); + if (!line.trim().isEmpty()) { + textToGoalObject(line); + } + } + s.close(); + } catch (FileNotFoundException fnf) { + System.out.println("Saved file cannot be found! FItNus will start with empty goal records."); + System.out.println(TextUi.buildingFileMsg()); + Duke.goalList = new GoalList(); + } catch (Exception e) { + System.out.println("Saved goal file is corrupted! FitNus will start with empty goal records."); + System.out.println(TextUi.buildingFileMsg()); + Duke.goalList = new GoalList(); //start with an empty goal list + } + } + + if (!dir.exists()) { + dir.mkdir(); + } + if (!textFile.exists()) { + textFile.createNewFile(); + Duke.goalList = new GoalList(); //start with an empty goal list + } + + } + + /** + * This method update the content of output Goal File by overwriting the file + * with all data saved in the current goalList + * Note that in the following implementation, the field writeFile in not used + * Instead, everytime a new file writer is created to update content of file + * @throws IOException if failed to access file + */ + public void overwriteGoalToFile() throws IOException { + String content = TextUi.contentOfGoalList(Duke.goalList); + if (content == null) { + return; + } + FileWriter fw = new FileWriter(textFile.toPath().toString(), false); + fw.write(content); + fw.close(); + } + + private static void textToGoalObject(String goalRecord) throws Exception { + String[] goalRecordParts = goalRecord.split(" ", 5); + //example of saved record: Consume 1230 kcal on Nov 11, 2023 + int amount =Integer.parseInt(goalRecordParts[1]); + String savedDateString = goalRecordParts[4]; + String date = convertDateFormat(savedDateString); + String restoredCommand = "set " + amount + " on " + date; + GoalList.addGoal(restoredCommand); + } + + + /** + * This method is used to convert a date String with format MMM d, yyyy into a date String with format dd/MM/yyyy + * @param originalDateString date String with format MMM d, yyyy, e.g. 11 Nov, 2023 + * @return date String with format dd/MM/yyyy, e.g. 11/11/2023 + */ + private static String convertDateFormat(String originalDateString) { + DateTimeFormatter originalFormatter = DateTimeFormatter.ofPattern("MMM d, yyyy", Locale.ENGLISH); + LocalDate originalDate = LocalDate.parse(originalDateString, originalFormatter); + DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("dd/M/yyyy"); + return originalDate.format(newFormatter); + } + + +} + + diff --git a/src/main/java/seedu/duke/storagefile/StorageFile.java b/src/main/java/seedu/duke/storagefile/StorageFile.java index 5b8243796a..9d9ba279ac 100644 --- a/src/main/java/seedu/duke/storagefile/StorageFile.java +++ b/src/main/java/seedu/duke/storagefile/StorageFile.java @@ -9,9 +9,9 @@ import seedu.duke.exerciselog.Log; public class StorageFile { - private static File dir; - private static File textFile; - private static FileWriter writeFile; + protected File dir; + protected File textFile; + protected FileWriter writeFile; public StorageFile(String dirName, String textFileName) { dir = new File(dirName); @@ -22,7 +22,7 @@ public static StorageFile initializeStorage(String dirName, String textFilePath) return new StorageFile(dirName, textFilePath); } - public void checkForTextFile(Log exerciseLog) throws IOException { + public void checkForLogTextFile(Log exerciseLog) throws IOException { if (dir.exists() && textFile.exists()) { try { Scanner s = new Scanner(textFile); @@ -64,6 +64,7 @@ public void writeExerciseToFile(int month, int day, String[] exerciseName, int c public void removeExerciseFromFile(int month, int day, String[] exerciseName, int caloriesBurned) throws IOException { + //duplicated code Scanner readFile = new Scanner(textFile); File tempFile = new File("./data/temp.txt"); FileWriter tempWriter = new FileWriter(tempFile.toPath().toString()); diff --git a/src/main/java/seedu/duke/ui/TextUi.java b/src/main/java/seedu/duke/ui/TextUi.java index a09bf85a6c..af12826d8d 100644 --- a/src/main/java/seedu/duke/ui/TextUi.java +++ b/src/main/java/seedu/duke/ui/TextUi.java @@ -16,6 +16,7 @@ import seedu.duke.Duke; import seedu.duke.commands.CommandResult; import seedu.duke.data.Goal; +import seedu.duke.data.GoalList; import seedu.duke.data.Printable; /** @@ -226,13 +227,13 @@ public static String showGoalList() { if (numberOfGoal == 0) { return "Oh not! You don't have any goal to achieve currently."; } - StringBuilder Sb = new StringBuilder(); - Sb.append("Here you go! Remember to stick to your exercise and meal plans.\n"); + StringBuilder sb = new StringBuilder(); + sb.append("Here you go! Remember to stick to your exercise and meal plans.\n"); for (int i = 0; i < numberOfGoal; i++){ - Sb.append(i + 1).append(". ").append(Duke.goalList.getGoal(i)).append("\n"); + sb.append(i + 1).append(". ").append(Duke.goalList.getGoal(i)).append("\n"); } - return Sb.toString(); + return sb.toString(); } /** @@ -244,13 +245,34 @@ public static String showAchievement() { if (numberOfGoal == 0) { return "Add oil! There is no achievement found."; } - StringBuilder Sb = new StringBuilder(); - Sb.append("Congratulation! See your achievements below: \n"); + StringBuilder sb = new StringBuilder(); + sb.append("Congratulation! See your achievements below: \n"); for (int i = 0; i < numberOfGoal; i++){ - Sb.append(i + 1).append(". [A]").append(Duke.achievedGoals.getGoal(i)).append("\n"); + sb.append(i + 1).append(". [A]").append(Duke.achievedGoals.getGoal(i)).append("\n"); } - return Sb.toString(); + return sb.toString(); + } + + /** + * This method return content of goal list in any goalList object + * It is typically used to overwrite save file whenever change in goal records + * @param goals a GoalList object + * @return String containing goal information of the goal object + */ + public static String contentOfGoalList(GoalList goals) { + if (goals.getGoalCount() == 0) { + return null; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < goals.getGoalCount(); i++){ + sb.append(goals.getGoal(i)).append("\n"); + } + return sb.toString(); + } + + public static String buildingFileMsg() { + return "Building new save file...\n" + "Building new file succeed!"; } }