diff --git a/collated/main/wenzongteo.md b/collated/main/wenzongteo.md index 5f2cf8c08ae9..776eb5eefb3e 100644 --- a/collated/main/wenzongteo.md +++ b/collated/main/wenzongteo.md @@ -2,13 +2,13 @@ ###### \java\seedu\address\logic\parser\AddCommandParser.java ``` java /** - * Check if the user has input any value for this variable before sending the input for further parsing. + * Check if the input is empty. If the input is not empty, return the input. Else, return UNFILLED_PARAMETER instead * * @param userInput Input entered by the user that is parsed by argMultimap. * @return the value of the input entered by the user or '-' if no input was entered. */ private static Optional checkInput(Optional userInput) { - return Optional.of(userInput.orElse("-")); + return Optional.of(userInput.orElse(UNFILLED_PARAMETER)); } } ``` @@ -18,11 +18,8 @@ * Check if data/images and data/edited folders exist. If they do not exist, create them. */ private void checkIfFilesExist() { - String imagesFolderPath = "data/images"; - String editedPhotoPath = "data/edited"; - - File imagesFolder = new File(imagesFolderPath); - File editedFolder = new File(editedPhotoPath); + File imagesFolder = new File(PHOTO_FOLDER); + File editedFolder = new File(EDITED_FOLDER); if (!imagesFolder.exists()) { imagesFolder.mkdirs(); @@ -43,7 +40,7 @@ * Deletes all existing images in data/edited folder first before deleting the folder itself. */ private void removeFiles() { - File toBeDeletedFolder = new File("data/edited"); + File toBeDeletedFolder = new File(EDITED_FOLDER); File[] toBeDeletedImages = toBeDeletedFolder.listFiles(); if (toBeDeletedImages != null) { for (File f : toBeDeletedImages) { @@ -54,17 +51,19 @@ } /** - * Copy the default.jpeg, which is used for the default image, into the /data/images/ folder. + * Copy default photo used in Augustine from the build resource. + * @param config Configurations Augustine is using. */ - private void checkDefaultImage() { + private void checkDefaultImage(Config config) { try { - InputStream is = this.getClass().getResourceAsStream("/images/default.jpeg"); + InputStream is = this.getClass().getResourceAsStream(config.getDefaultPhoto()); byte[] buffer = new byte[is.available()]; is.read(buffer); - File targetFile = new File("data/images/default.jpeg"); + File targetFile = new File(DEFAULT_PHOTO); OutputStream outStream = new FileOutputStream(targetFile); outStream.write(buffer); + logger.info("Copying default photo over to " + DEFAULT_PHOTO); } catch (IOException ioe) { throw new AssertionError("Impossible"); @@ -74,6 +73,65 @@ } } ``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Deletes all existing images in data/edited folder first before deleting the folder itself. + */ + private void moveFilesToEditedFolder() { + String photoFolder = "data/images/"; + File toBeDeletedFolder = new File(photoFolder); + File[] toBeDeletedImages = toBeDeletedFolder.listFiles(); + + if (toBeDeletedImages != null) { + for (File f : toBeDeletedImages) { + removePhoto(f); + } + } + } + + /** + * Check if photo in image is the default photo. If it is, ignore. Else, move to edited folder. + * Add into the photoStack in UniquePersonList to record the latest image file. + * @param f current photo to check. + */ + private void removePhoto(File f) { + String defaultPhoto = "data/images/default.jpeg"; + try { + if (!f.equals(new File(defaultPhoto))) { + String destPath = f.toString().substring(12); + destPath = getEditedPhotoPath(destPath); + + Files.copy(f.toPath(), Paths.get(destPath), StandardCopyOption.REPLACE_EXISTING); + UniquePersonList.addToPhotoStack(destPath); + f.delete(); + } + } catch (IOException ioe) { + throw new AssertionError("Clearing of files have issue"); + } + } + + /** + * Check if there are existing photos from the same contact in the data/edited/ folder. if there are, add a counter + * to the file name so that the photos will not be overwritten. + * @param originalPath photo path in data/images + * @return the updated photo path to data/edited/ folder. + */ + private String getEditedPhotoPath(String originalPath) { + String editedFolder = "data/edited/"; + String destPath = editedFolder + originalPath; + String emailAddr = originalPath.substring(0, originalPath.length() - 4); + String fileExt = ".jpg"; + int counter = 0; + + while (FileUtil.isFileExists(new File(destPath))) { + counter++; + destPath = editedFolder + emailAddr + counter + fileExt; + } + + return destPath; + } +``` ###### \java\seedu\address\model\ModelManager.java ``` java @Override @@ -106,7 +164,9 @@ */ public class Photo { public static final String MESSAGE_PHOTO_CONSTRAINTS = - "Person's photo should be in jpeg and preferred to be of 340px x 453px dimension"; + "Person's photo should be in .jpg or .jpeg and preferred to be of 340px x 453px dimension. If the " + + "photo is on the local system, please provide the absolute file path. If the photo is from the " + + "internet, ensure that the link starts with http or https and ends with .jpg or .jpeg"; public static final String MESSAGE_PHOTO_NOT_FOUND = "Error! Photo does not exist!"; public static final String MESSAGE_LINK_ERROR = "Error! URL given is invalid!"; @@ -114,9 +174,15 @@ public class Photo { * Can contain multiple words but must end with .jpg or .jpeg */ public static final String PHOTO_VALIDATION_REGEX = "([^\\s]+[\\s\\w]*(\\.(?i)(jpg|jpeg|))$)"; - public static final String URL_REGEX = "\\b(https?)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"; + /** + * Can contain multiple words or symbols but must start with either http or https and end with either .jpg or .jpeg + */ + public static final String URL_REGEX = + "((^(https?)(://))[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]*(\\.(?i)(jpg|jpeg))$)"; public static final String DEFAULT_PHOTO = "data/images/default.jpeg"; - public static final String tempStorage = "data/temporary.jpg"; + public static final String DOWNLOAD_LOCATION = "data/download.jpg"; + public static final String UNFILLED = "-"; + private static final Logger logger = LogsCenter.getLogger(MainApp.class); public final String value; private final String hash; @@ -128,29 +194,17 @@ public class Photo { public Photo(String photo) throws IllegalValueException { photo = photo.trim(); - if (photo.equals("-")) { - photo = "data/images/default.jpeg"; + if (photo.equals(UNFILLED)) { + logger.info("photo fill is left unfilled, using default photo as contact's photo."); + photo = DEFAULT_PHOTO; } if (!isValidPhoto(photo)) { throw new IllegalValueException(MESSAGE_PHOTO_CONSTRAINTS); } else { - if (photo.matches(URL_REGEX)) { - this.value = downloadFromInternet(photo); - } else { - File image = new File(photo); - if (!FileUtil.isFileExists(image)) { - throw new IllegalValueException(MESSAGE_PHOTO_NOT_FOUND); - } else { - this.value = photo; - } - } + this.value = getPhoto(photo); File image = new File(this.value); - try { - this.hash = generateHash(image); - } catch (NoSuchAlgorithmException | IOException e) { - throw new AssertionError("Impossible to reach here"); - } + this.hash = generateHash(image); } } @@ -161,19 +215,58 @@ public class Photo { * @param num used for overloading constructor. */ public Photo(String photo, int num) { + photo = photo.trim(); + File image = new File(photo); this.value = photo; + if (!FileUtil.isFileExists(image)) { + logger.info("contact's photo cannot be found. Using default photo instead."); + copyDefaultPhoto(photo); + } + this.hash = generateHash(image); + } + + /** + * Check if photo source given is from the internet or local system and call the relevant methods. + * Return the path for the photo that is obtained from the relevant methods. + * @param photo photo path specified by the user. + * @return photo path of the photo that is obtained from the relevant methods. + * @throws IllegalValueException if the photo is not found. + */ + private String getPhoto(String photo) throws IllegalValueException { + if (photo.matches(URL_REGEX)) { + logger.info("Attempting to download photo from the internet"); + return getPhotoFromInternet(photo); + } else { + return getPhotoFromLocal(photo); + } + } + + /** + * Check if the file exists in the local system. Return IllegalValueException if the image is not found. + * @param photo photo path specified by the user. + * @return photo path specified by the user if the photo exists. + * @throws IllegalValueException if the photo is not found on the local system + */ + private String getPhotoFromLocal(String photo) throws IllegalValueException { + File image = new File(photo); + if (!FileUtil.isFileExists(image)) { + throw new IllegalValueException(MESSAGE_PHOTO_NOT_FOUND); + } else { + return photo; + } + } + + /** + * Copy the default photo for contact to serve as the contact's photo. + * @param destPath File path of the contact's photo. + */ + public void copyDefaultPhoto(String destPath) { try { - if (!FileUtil.isFileExists(image)) { - Files.copy(Paths.get(DEFAULT_PHOTO), Paths.get(photo), StandardCopyOption.REPLACE_EXISTING); - } else { - } - this.hash = generateHash(image); - } catch (NoSuchAlgorithmException nsa) { - throw new AssertionError("Algorithm should exist"); + Files.copy(Paths.get(DEFAULT_PHOTO), Paths.get(destPath), StandardCopyOption.REPLACE_EXISTING); } catch (IOException ioe) { - throw new AssertionError("Image should already exist"); + throw new AssertionError("Photo should already exist!"); } } @@ -182,16 +275,26 @@ public class Photo { * @throws IOException if the file does not exist. * @throws NoSuchAlgorithmException if the algorithm does not exist. */ - public String generateHash(File photo) throws IOException, NoSuchAlgorithmException { - MessageDigest hashing = MessageDigest.getInstance("MD5"); - return new String(hashing.digest(Files.readAllBytes(photo.toPath()))); + public String generateHash(File photo) { + try { + MessageDigest hashing = MessageDigest.getInstance("MD5"); + return new String(hashing.digest(Files.readAllBytes(photo.toPath()))); + } catch (Exception e) { + throw new AssertionError("Impossible"); + } } /** * @return true if a given string is a valid person photo. */ public static boolean isValidPhoto(String test) { - return test.matches(PHOTO_VALIDATION_REGEX); + if (test.matches(URL_REGEX)) { + return true; + } else if (test.matches(PHOTO_VALIDATION_REGEX)) { + return true; + } else { + return false; + } } /** @@ -202,16 +305,16 @@ public class Photo { } /** - * Downloads photo from the link given onto the computer and store it locally. + * Downloads photo from the path given by the user and store it locally. Returns the local file path of the image. * @param photo URL link given by the user - * @return the temporary storage location of the downloaded image. - * @throws IllegalValueException if errors are faced when writing file onto local drive. + * @return the file path of the downloaded image in the local system. + * @throws IllegalValueException if errors are faced when visiting the link or downloading the file. */ - private String downloadFromInternet(String photo) throws IllegalValueException { + private String getPhotoFromInternet(String photo) throws IllegalValueException { try { URL url = new URL(photo); InputStream is = url.openStream(); - OutputStream os = new FileOutputStream(tempStorage); + OutputStream os = new FileOutputStream(DOWNLOAD_LOCATION); byte[] buffer = new byte[4096]; int length = 0; @@ -223,13 +326,10 @@ public class Photo { is.close(); os.close(); - return tempStorage; - } catch (MalformedURLException mue) { + logger.info("Photo downloaded to " + DOWNLOAD_LOCATION); + return DOWNLOAD_LOCATION; + } catch (IOException e) { throw new IllegalValueException(MESSAGE_LINK_ERROR); - } catch (IOException ioe) { - throw new AssertionError("Read / Write issue"); - } catch (Exception e) { - throw new AssertionError("Impossible to reach here"); } } @@ -253,6 +353,14 @@ public class Photo { ``` ###### \java\seedu\address\model\person\UniquePersonList.java ``` java + /** + * Accessor to allow other classes to push into the edited photo stack when they delete photos from Augustine. + * @param photoPath photoPath in data/edited/ folder. + */ + public static void addToPhotoStack(String photoPath) { + photoStack.push(photoPath); + } + /** * Calculates the MD5 hash value of the person's display picture in {@code srcPath}. * returns the calculated hash in {@code hashValue}. @@ -276,8 +384,17 @@ public class Photo { * @throws IOException if the srcPath cannot be found in the system. */ public void createBackUpPhoto(String srcPath, String emailAddr) throws IOException { - String destPath = "data/edited/" + emailAddr + ".jpg"; + String destPath = editedFolder + emailAddr + photoFileType; + int counter = 0; + + while (FileUtil.isFileExists(new File(destPath))) { + counter++; + destPath = editedFolder + emailAddr + counter + photoFileType; + } + Files.copy(Paths.get(srcPath), Paths.get(destPath), StandardCopyOption.REPLACE_EXISTING); + photoStack.push(destPath); + logger.info("Image for " + emailAddr + photoFileType + " copied to " + editedFolder); } /** @@ -286,8 +403,11 @@ public class Photo { * @throws IOException if the srcPath cannot be found in the system. */ public void createCurrentPhoto(String srcPath, String emailAddr) throws IOException { - String destPath = "data/images/" + emailAddr + ".jpg"; - Files.copy(Paths.get(srcPath), Paths.get(destPath), StandardCopyOption.REPLACE_EXISTING); + String destPath = photoFolder + emailAddr + photoFileType; + if (!srcPath.equals(destPath)) { + Files.copy(Paths.get(srcPath), Paths.get(destPath), StandardCopyOption.REPLACE_EXISTING); + } + logger.info("Image for " + emailAddr + photoFileType + " copied to " + photoFolder); } /** @@ -297,6 +417,7 @@ public class Photo { */ public void deleteExistingPhoto(String srcPath) throws IOException { Files.delete(Paths.get(srcPath)); + logger.info("Image " + srcPath + " deleted"); } /** @@ -308,6 +429,68 @@ public class Photo { person.setPhoto(new Photo(srcPath, 0)); return person; } + + /** + * Check if the photo or email of the person is changed during the edit command. + * + * @param target Original Person in Augustine + * @param editedPerson Edited Person by the user + * @return which case of change occurred. + */ + public int updateCasesForPhoto(ReadOnlyPerson target, ReadOnlyPerson editedPerson) { + if (target.getEmailAddress().equals(editedPerson.getEmailAddress()) + && !target.getPhoto().equals(editedPerson.getPhoto())) { //Only Photo changed. + return ONLY_PHOTO_CHANGED; + } else if (!target.getEmailAddress().equals(editedPerson.getEmailAddress()) + && target.getPhoto().equals(editedPerson.getPhoto())) { //only email changed. + return ONLY_EMAIL_CHANGED; + } else if (!target.getEmailAddress().equals(editedPerson.getEmailAddress()) + && !target.getPhoto().equals(editedPerson.getPhoto())) { //Both changed. + return BOTH_PHOTO_AND_EMAIL_CHANGED; + } else if (target.getEmailAddress().equals(editedPerson.getEmailAddress()) + && target.getPhoto().equals(editedPerson.getPhoto())) { //No special update + return NEITHER_PHOTO_OR_EMAIL_CHANGED; + } else { + throw new AssertionError("Shouldn't be here"); + } + } + + /** + * Copy the contact's photo from data/edited folder to data/images folder when an undo command is executed. + * + * @param person the edited person. + * @param toBeCopied Previous photo of the person. + */ + private void copyBackupPhoto (ReadOnlyPerson person, File toBeCopied) { + try { + if (FileUtil.isFileExists(toBeCopied)) { + createCurrentPhoto(toBeCopied.toString(), person.getEmailAddress().toString()); + } + } catch (IOException ioe) { + throw new AssertionError("Photo does not exist"); + } + } + + /** + * Compare the hash of the contact's current photo and the stored photo in the Person's Photo Object to ensure that + * the photo is correct. + * + * @param person person who the photos belong to. + * @param toBeCopied previous photo of the person. + */ + private void comparePhotoHash (ReadOnlyPerson person, File toBeCopied) { + try { + String hashValue = calculateHash(person.getPhoto().toString()); + if (!hashValue.equals(person.getPhoto().getHash())) { //Not equal, go take the old image + createCurrentPhoto(photoStack.pop(), person.getEmailAddress().toString()); + //createCurrentPhoto(toBeCopied.toString(), person.getEmailAddress().toString()); + } + } catch (IOException ioe) { + throw new AssertionError("Photo does not exist!"); + } catch (NoSuchAlgorithmException nsa) { + throw new AssertionError("Impossible"); + } + } ``` ###### \java\seedu\address\ui\PersonCard.java ``` java diff --git a/collated/test/wenzongteo.md b/collated/test/wenzongteo.md index 28645f9991ff..549375fdb3f7 100644 --- a/collated/test/wenzongteo.md +++ b/collated/test/wenzongteo.md @@ -70,12 +70,12 @@ public class PhotoTest { try { new Photo("doesnotexist.jpg"); } catch (IllegalValueException ioe) { - assertEquals("Error! Photo does not exist!", ioe.getMessage()); + assertEquals(Photo.MESSAGE_PHOTO_NOT_FOUND , ioe.getMessage()); } try { new Photo("doesnotexist.jpg", 0); } catch (AssertionError ae) { - assertEquals("Image should already exist", ae.getMessage()); + assertEquals("Photo should already exist!", ae.getMessage()); } } @@ -84,8 +84,7 @@ public class PhotoTest { try { new Photo("wrong format"); } catch (IllegalValueException ive) { - assertEquals("Person's photo should be in jpeg and preferred to be of 340px x 453px dimension", - ive.getMessage()); + assertEquals(Photo.MESSAGE_PHOTO_CONSTRAINTS, ive.getMessage()); } } @@ -167,6 +166,7 @@ public class ImageInit { private static final String IMAGES_FOLDER_PATH = "data/images"; private static final String DEFAULT_ORIGINAL_PATH = "default.jpeg"; private static final String ALICE_PHOTO_PATH = "data/images/alice@example.com.jpg"; + private static final String DEFAULT_PHOTO_PATH = "data/images/default.jpeg"; private static final String JOHN_PHOTO_PATH = "data/images/johnd@example.com.jpg"; private static final String HEINZ_PHOTO_PATH = "data/images/heinz@example.com.jpg"; private static final String ANNA_PHOTO_PATH = "data/images/anna@example.com.jpg"; @@ -249,14 +249,28 @@ public class ImageInit { } /** - * Initialize alice's photo during the test. + * Initialize john's photo during the test. */ public static void initJohn() { try { Files.copy(Paths.get(DEFAULT_ORIGINAL_PATH), Paths.get(JOHN_PHOTO_PATH), StandardCopyOption.REPLACE_EXISTING); } catch (IOException ioe) { - throw new AssertionError("Initialization of Alice's image failed"); + throw new AssertionError("Initialization of John's image failed"); + } catch (Exception e) { + throw new AssertionError("No such error possible"); + } + } + + /** + * Initialize default photo during the test. + */ + public static void initDefault() { + try { + Files.copy(Paths.get(DEFAULT_ORIGINAL_PATH), Paths.get(DEFAULT_PHOTO_PATH), + StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ioe) { + throw new AssertionError("Initialization of default image failed"); } catch (Exception e) { throw new AssertionError("No such error possible"); } diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 0d0568122003..a6c2ebb1694f 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -103,6 +103,7 @@ private void moveFilesToEditedFolder() { /** * Check if photo in image is the default photo. If it is, ignore. Else, move to edited folder. + * Add into the photoStack in UniquePersonList to record the latest image file. * @param f current photo to check. */ private void removePhoto(File f) { @@ -122,7 +123,8 @@ private void removePhoto(File f) { } /** - * Check if there are existing photos from the same contact in the data/edited/ folder. + * Check if there are existing photos from the same contact in the data/edited/ folder. if there are, add a counter + * to the file name so that the photos will not be overwritten. * @param originalPath photo path in data/images * @return the updated photo path to data/edited/ folder. */ @@ -132,10 +134,12 @@ private String getEditedPhotoPath(String originalPath) { String emailAddr = originalPath.substring(0, originalPath.length() - 4); String fileExt = ".jpg"; int counter = 0; + while (FileUtil.isFileExists(new File(destPath))) { counter++; destPath = editedFolder + emailAddr + counter + fileExt; } + return destPath; } //@@author diff --git a/src/main/java/seedu/address/model/person/Photo.java b/src/main/java/seedu/address/model/person/Photo.java index ed3fc98ff795..4b159af9cc31 100644 --- a/src/main/java/seedu/address/model/person/Photo.java +++ b/src/main/java/seedu/address/model/person/Photo.java @@ -38,7 +38,8 @@ public class Photo { /** * Can contain multiple words or symbols but must start with either http or https and end with either .jpg or .jpeg */ - public static final String URL_REGEX = "\\b(https?)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"; + public static final String URL_REGEX = + "((^(https?)(://))[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]*(\\.(?i)(jpg|jpeg))$)"; public static final String DEFAULT_PHOTO = "data/images/default.jpeg"; public static final String DOWNLOAD_LOCATION = "data/download.jpg"; public static final String UNFILLED = "-"; @@ -148,7 +149,13 @@ public String generateHash(File photo) { * @return true if a given string is a valid person photo. */ public static boolean isValidPhoto(String test) { - return (test.matches(PHOTO_VALIDATION_REGEX) || test.matches(URL_REGEX)); + if (test.matches(URL_REGEX)) { + return true; + } else if (test.matches(PHOTO_VALIDATION_REGEX)) { + return true; + } else { + return false; + } } /**