SDPepe/AppArt

View on GitHub
app/src/main/java/ch/epfl/sdp/appart/database/local/LocalAdWriter.java

Summary

Maintainability
A
0 mins
Test Coverage
A
97%
package ch.epfl.sdp.appart.database.local;

import android.graphics.Bitmap;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;

import ch.epfl.sdp.appart.ad.Ad;
import ch.epfl.sdp.appart.database.exceptions.LocalDatabaseException;
import ch.epfl.sdp.appart.database.firebaselayout.AdLayout;
import ch.epfl.sdp.appart.utils.FileIO;
import ch.epfl.sdp.appart.utils.serializers.AdSerializer;

/**
 * This class manages everything related to writing an ad on disk
 */
public class LocalAdWriter {

    /**
     * This type is used to tell some methods with which type of image we are
     * working on. It is either a PHOTO or a PANORAMA.
     */
    private enum ImageType {
        PHOTO, PANORAMA
    }

    /**
     * This function creates the folder for the local ad. If it already
     * exists it calls removeExtraPhotos to make sure we do not leave any
     * invalid data in the folder. It does not need to remove anything else
     * since the ad data will be stored in a file that will be overwritten.
     * The actual operation is performed in {@link FileIO} by the
     * createFolder method.
     *
     * @param futureNumberOfPhotos    the number of photos of the newly updated
     *                                ad, its sole purpose is to be used when
     *                                the ad already exists on disk and we want
     *                                to make sure we do not leave any extra
     *                                photos.
     * @param futureNumberOfPanoramas the number of panoramas.
     * @param currentUserID           the current user ID
     * @return a boolean that indicates the success of the operation
     */
    static boolean createAdFolder(int futureNumberOfPhotos,
                                  int futureNumberOfPanoramas,
                                  String currentUserID) {
        return FileIO.createFolderOrElse(LocalDatabasePaths.cardFolder(currentUserID),
                () -> removeExtraPhotos(futureNumberOfPhotos, currentUserID) &&
                        removeExtraPanoramas(futureNumberOfPanoramas,
                                currentUserID));
    }


    /**
     * This function allows us to retrieve the number of photos of the ad if
     * it already exists in the favorites folder. The ad is specified by the
     * caller which sets the cardId value in the {@link LocalDatabasePaths}
     * class.
     *
     * @param currentUserID the current user ID
     * @return an int that represents the number of photos of the ad
     */
    private static int getNumberOfPhotos(String currentUserID) {
        String dataPath =
                LocalDatabasePaths.cardData(currentUserID);

        Map<String, Object> adMap = FileIO.readMapObject(dataPath);
        if (adMap == null) return 0;
        @SuppressWarnings("unchecked") List<String> localPhotoRefs =
                (List<String>) adMap.get(AdLayout.PHOTO_REFS);
        return Objects.requireNonNull(localPhotoRefs).size();
    }

    /**
     * This function allows us to retrieve the number of panoramas of the ad if
     * it already exists in the favorites folder. The ad is specified
     * by the caller which sets the cardId value in the
     * {@link LocalDatabasePaths} class.
     *
     * @param currentUserID the current user ID
     * @return an int that represents the number of panoramas of the ad
     */
    private static int getNumberOfPanoramas(String currentUserID) {
        String dataPath =
                LocalDatabasePaths.cardData(currentUserID);


        Map<String, Object> adMap = FileIO.readMapObject(dataPath);
        if (adMap == null) return 0;
        //noinspection unchecked
        List<String> localPanoramaRefs =
                (List<String>) adMap.get(AdLayout.PANORAMA_REFS);
        return Objects.requireNonNull(localPanoramaRefs).size();
    }

    /**
     * This function allows to remove the extra photos that might stay on
     * disk if the newly updated ad has less photos than the previous version.
     * The actual operation is performed by removeExtraPictures.
     *
     * @param futureNumberOfPhotos the number of photos the updated version
     *                             of the ad
     * @param currentUserID        the current user ID
     * @return a boolean that indicates if the operation succeeded or not
     */
    private static boolean removeExtraPhotos(int futureNumberOfPhotos,
                                             String currentUserID) {
        return removeExtraPictures(getNumberOfPhotos(currentUserID),
                futureNumberOfPhotos,
                ImageType.PHOTO, currentUserID);
    }

    /**
     * This function handles the removing of extra pictures in case the
     * newest version of an ad has less pictures than the previous one. It
     * handles PHOTO and PANORAMA images.
     *
     * @param curNumberOfPics    the number of pictures of the previous
     *                           version of the ad
     * @param futureNumberOfPics the number of pictures of the newest version
     *                           of the ad
     * @param type               either ImageType.PHOTO or ImageType.PANORAMA.
     * @param currentUserID      the current user ID
     * @return true if the operation succeeded, false otherwise
     */
    private static boolean removeExtraPictures(int curNumberOfPics,
                                               int futureNumberOfPics,
                                               ImageType type,
                                               String currentUserID) {
        int deletedFiles = 0;
        if (curNumberOfPics > futureNumberOfPics) {
            for (int i = futureNumberOfPics; i < curNumberOfPics; ++i) {
                String photoPath;
                //Maybe move this outside the for loop
                if (type == ImageType.PHOTO) {
                    photoPath =
                            LocalDatabasePaths.photoFile(currentUserID, i);
                } else {
                    photoPath =
                            LocalDatabasePaths.panoramaFile(currentUserID, i);
                }
                File photoToDelete =
                        new File(Objects.requireNonNull(photoPath));
                if (photoToDelete.exists()) {
                    if (photoToDelete.delete()) {
                        deletedFiles++;
                    }
                }
            }
            return deletedFiles == (curNumberOfPics - futureNumberOfPics);
        }
        return true;
    }

    /**
     * This function allows to remove the extra panoramas that might stay on
     * disk if the newly updated ad has less panoramas than the previous
     * version.
     * The actual operation is performed by removeExtraPictures.
     *
     * @param futureNumberOfPanoramas the number of panoramas the updated
     *                                version
     *                                of the ad
     * @param currentUserID           the current user ID
     * @return a boolean that indicates if the operation succeeded or not
     */
    private static boolean removeExtraPanoramas(int futureNumberOfPanoramas,
                                                String currentUserID) {
        return removeExtraPictures(getNumberOfPanoramas(currentUserID),
                futureNumberOfPanoramas,
                ImageType.PANORAMA, currentUserID);
    }

    /**
     * This builds the local refs for the pictures of an ad. Since the pictures
     * are now stored on disk, we replace the references to the online
     * database by "real" references to "on disk" files. It handles panoramas
     * and photos.
     *
     * @param size          the number of refs of the ad
     * @param type          the type of pictures we are handling, either
     *                      PHOTOs or
     *                      PANORAMAs
     * @param currentUserID the current user ids
     * @return the new list of local refs
     */
    /**
     * This builds the local refs for the pictures of an ad. Since the pictures
     * are now stored on disk, we replace the references to the online
     * database by "real" references to "on disk" files. It handles panoramas
     * and photos.
     *
     * @param sizePhotos         the number of photos in the ad
     * @param sizePanoramas      the number of panoramas in the ad
     * @param currentUserID      the current user id
     * @param photoReferences    a list that will be filled with the local
     *                           photo references
     * @param panoramaReferences a list that will be filled with the local
     *                           panorama references
     */
    private static void buildLocalReferences(int sizePhotos, int sizePanoramas,
                                             String currentUserID,
                                             List<String> photoReferences,
                                             List<String> panoramaReferences) {
        for (int i = 0; i < sizePhotos; ++i) {
            photoReferences.add(LocalDatabasePaths.photoFile(currentUserID, i));
        }
        for (int i = 0; i < sizePanoramas; ++i) {
            panoramaReferences.add(LocalDatabasePaths.panoramaFile(currentUserID, i));
        }
    }

    /**
     * Constructs a local ad from an "online" ad. The only difference is the
     * references to panoramas and photos. The local ad will have paths to
     * files "on disk" has its references for the pictures.
     *
     * @param ad            the original "online" ad
     * @param currentUserID the current user ID
     * @return a local ad, that is to say, an ad with updated local references.
     */
    static Ad buildLocalAd(Ad ad, String currentUserID) {
        List<String> localPhotoRefs = new ArrayList<>();
        List<String> localPanoramaRefs = new ArrayList<>();
        buildLocalReferences(ad.getPhotosRefs().size(),
                ad.getPanoramaReferences().size(), currentUserID,
                localPhotoRefs, localPanoramaRefs);

        return new Ad(ad.getTitle(), ad.getPrice(), ad.getPricePeriod()
                , ad.getStreet(), ad.getCity(), ad.getAdvertiserName(),
                ad.getAdvertiserId(),
                ad.getDescription(), localPhotoRefs,
                localPanoramaRefs, ad.hasVRTour());
    }

    /**
     * This method writes an ad on disk. If the folder doesn't already
     * exists, it makes sure to create one.
     *
     * @param adID          the id of the ad
     * @param localAd       the local ad
     * @param userID        the id of the user
     * @param cardID        the id of the card
     * @param currentUserID the current user ID
     * @return a boolean that indicates if the operation succeeded or not
     */
    static boolean writeAd(String adID, Ad localAd, String userID,
                           String cardID, String currentUserID) {
        //Serializing
        Map<String, Object> adMap = AdSerializer.serializeLocal(localAd, adID
                , cardID, userID);

        //Write file on disk
        return FileIO.writeMapObject(LocalDatabasePaths.cardData(currentUserID), adMap);
    }

    /**
     * This method writes the ad photos on disk. The actual operation is
     * performed by the writeImage function. The writing happens asynchronously.
     *
     * @param adPhotos      list of bitmaps that represents the ad photos
     * @param currentUserID the current user ID
     * @param cardID        the card id
     * @return a completable future that indicates if the operation succeeded
     * or not
     */
    static CompletableFuture<Void> writeAdPhotos(List<Bitmap> adPhotos,
                                                 String currentUserID,
                                                 String cardID) {
        return writeImages(adPhotos, ImageType.PHOTO, currentUserID, cardID);
    }

    /**
     * This method performs the writes of images on disk. It can handle
     * photos and panoramas. The actual operation is performed in
     * {@link FileIO} by the saveBitmap method. The writing happens
     * asynchronously.
     *
     * @param bitmaps       the list of bitmaps representing the images
     * @param type          the type of images
     * @param cardID        the card id
     * @param currentUserID the current user ID
     * @return a completable future that indicates if the operation succeeded
     * or not.
     */
    private static CompletableFuture<Void> writeImages(List<Bitmap> bitmaps,
                                                       ImageType type,
                                                       String currentUserID,
                                                       String cardID) {


        return CompletableFuture.runAsync(() -> {
            boolean photoSaveSuccess = true;
            for (int i = 0; i < bitmaps.size(); ++i) {

                String bitmapPath;
                //Strings are immutable so it should be ok to give
                // them like
                // this I guess
                if (type == ImageType.PHOTO) {
                    bitmapPath =
                            LocalDatabasePaths.photoFile(currentUserID,
                                    cardID, i);
                } else {
                    bitmapPath =
                            LocalDatabasePaths.panoramaFile(currentUserID,
                                    cardID, i);
                }
                photoSaveSuccess &= FileIO.saveBitmap(bitmaps.get(i),
                        bitmapPath);
            }
            if (!photoSaveSuccess)
                throw new CompletionException(new LocalDatabaseException(
                        "Could not write all the images !"));
        });
    }

    /**
     * This method writes the ad panoramas on disk. The actual operation is
     * performed by the writeImage function.
     *
     * @param panoramas     list of bitmaps that represents the ad panoramas
     * @param currentUserID the current user ID
     * @param cardID        the card id
     * @return a boolean that indicates if the operation succeeded or not
     */
    static CompletableFuture<Void> writePanoramas(List<Bitmap> panoramas,
                                                  String currentUserID,
                                                  String cardID) {
        return writeImages(panoramas,
                ImageType.PANORAMA, currentUserID, cardID);
    }
}