All files / lib/sync-engine/helpers write-albums-helper.ts

100% Statements 132/132
100% Branches 23/23
100% Functions 5/5
100% Lines 132/132

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 1321x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10x 10x 10x 10x 10x 10x 10x 10x 10x 17x 10x 10x 10x 12x 10x 10x 10x 10x 1x 1x 1x 1x 1x 1x 1x 1x 1x 12x 12x 12x 12x 3x 3x 3x 1x 1x 1x 1x 3x 3x 3x 9x 9x 9x 12x 1x 1x 1x 1x 1x 12x 1x 1x 1x 1x 1x 1x 1x 17x 17x 17x 4x 4x 4x 1x 1x 1x 1x 4x 4x 4x 13x 13x 13x 17x 1x 1x 1x 1x 17x 1x 1x 1x 1x 1x 1x 1x 1x 24x 24x 1x 1x 1x 1x 1x 1x 1x 1x 1x 118x 6x 6x 112x 118x 26x 26x 86x 118x 26x 26x 60x 60x 60x 60x 60x 118x 4x 4x 118x
import {HANDLER_EVENT} from "../../../app/event/error-handler.js";
import {iCPSError} from "../../../app/error/error.js";
import {Album, AlbumType} from "../../photos-library/model/album.js";
import {PLibraryProcessingQueues} from "../../photos-library/model/photos-entity.js";
import {SyncEngine} from "../sync-engine.js";
import {SYNC_ERR} from "../../../app/error/error-codes.js";
 
/**
 * Writes the album changes defined in the processing queue to to disk
 * @param processingQueue - The album processing queue, expected to have resolved all hierarchical dependencies
 * @returns A promise that settles, once all album changes have been written to disk
 */
export async function writeAlbums(this: SyncEngine, processingQueue: PLibraryProcessingQueues<Album>) {
    this.logger.info(`Writing lib structure!`);
 
    // Making sure our queues are sorted
    const toBeDeleted: Album[] = this.sortQueue(processingQueue[0]);
    const toBeAdded: Album[] = this.sortQueue(processingQueue[1]);
 
    // Deletion before addition, in order to avoid duplicate folders
    // Reversing processing order, since we need to remove nested folders first
    toBeDeleted.reverse().forEach(album => {
        this.removeAlbum(album);
    });
 
    toBeAdded.forEach(album => {
        this.addAlbum(album);
    });
 
    await this.photosLibrary.cleanArchivedOrphans();
}
 
/**
 * Writes the data structure of an album to disk. This includes:
 *   * Create a hidden folder containing the UUID
 *   * Create a link to the hidden folder, containing the real name of the album
 *   * (If possible) link correct pictures from the assetFolder to the newly created album
 * @param album - The album, that should be written to disk
 */
export function addAlbum(this: SyncEngine, album: Album) {
    // If albumType == Archive -> Check in 'archivedFolder' and move
    this.logger.debug(`Creating album ${album.getDisplayName()} with parent ${album.parentAlbumUUID}`);
 
    if (album.albumType === AlbumType.ARCHIVED) {
        try {
            this.photosLibrary.retrieveStashedAlbum(album);
        } catch (err) {
            this.emit(HANDLER_EVENT, new iCPSError(SYNC_ERR.STASH_RETRIEVE)
                .addMessage(album.getDisplayName())
                .addCause(err));
        }
 
        return;
    }
 
    try {
        this.photosLibrary.writeAlbum(album);
    } catch (err) {
        this.emit(HANDLER_EVENT, new iCPSError(SYNC_ERR.ADD_ALBUM)
            .addMessage(album.getDisplayName())
            .addCause(err)
            .setWarning());
    }
}
 
/**
 * This will delete an album from disk and remove all associated symlinks
 * Deletion will only happen if the album is 'empty'. This means it only contains symlinks or 'safe' files. Any other folder or file will result in the folder not being deleted.
 * @param album - The album that needs to be deleted
 */
export function removeAlbum(this: SyncEngine, album: Album) {
    this.logger.debug(`Removing album ${album.getDisplayName()}`);
 
    if (album.albumType === AlbumType.ARCHIVED) {
        try {
            this.photosLibrary.stashArchivedAlbum(album);
        } catch (err) {
            this.emit(HANDLER_EVENT, new iCPSError(SYNC_ERR.STASH)
                .addMessage(album.getDisplayName())
                .addCause(err));
        }
 
        return;
    }
 
    try {
        this.photosLibrary.deleteAlbum(album);
    } catch (err) {
        this.emit(HANDLER_EVENT, new iCPSError(SYNC_ERR.DELETE_ALBUM)
            .addMessage(album.getDisplayName())
            .addCause(err));
    }
}
 
/**
 * This function will sort a given queue. The sort is performed on a copy of the array, referencing the same objects.
 * Order is defined as follows: For every album in the array, its parent's index (if exists) is always smaller than the index of the album (parent is 'in front' of all of its children)
 * @param unsortedQueue - The unsorted queue.
 * @returns A sorted queue
 */
export function sortQueue(this: SyncEngine, unsortedQueue: Album[]): Album[] {
    return [...unsortedQueue].sort((a, b) => SyncEngine.compareQueueElements(unsortedQueue, a, b));
}
 
/**
 * Compares two queue elements, based on the specification of compareFn of Array.sort
 * @param fullQueue - The full queue necessary to check for ancestors
 * @param a - The first element
 * @param b - The second element
 * @returns - Returns a negative value if the first element is less than the second element, zero if they're equal, and a positive value otherwise.
 */
export function compareQueueElements(fullQueue: Album[], a: Album, b: Album): number {
    if (a.getUUID() === b.getUUID()) {
        return 0;
    }
 
    if (a.hasAncestor(b, fullQueue)) {
        return 1; // B is ancestor, therefore his index needs to be bigger
    }
 
    if (b.hasAncestor(a, fullQueue)) {
        return -1; // A is ancestor, therefore his index needs to be bigger
    }
 
    try {
        const distanceToRootA = Album.distanceToRoot(a, fullQueue);
        const distanceToRootB = Album.distanceToRoot(b, fullQueue);
        return distanceToRootA - distanceToRootB; // Provide distance based on depth
    } catch (err) {
        return 0; // If there is a broke in the link, return them as equal
    }
}