Jacek Kowalski
4 hours ago 6b9278a5d57091fce636dbb640b380cfb990db6c
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
132
<?php
declare(strict_types=1);
 
namespace App\Service;
 
use App\Entity\Book;
use App\Entity\OustedBook;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
 
/**
 * Operations that change where a book is: lent out, back on the shelf, out of the collection.
 */
class Library {
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly CoverStorage           $covers,
        private readonly LibraryThingCovers     $libraryThing,
    ) {
    }
 
    /**
     * Gives a book without a cover the one LibraryThing has for its ISBN, if any.
     *
     * @return bool whether a cover was added
     */
    public function downloadCover(Book $book): bool {
        $isbn = $book->getIsbn();
 
        if ($book->getCover() !== null || $isbn === null || !$this->libraryThing->isEnabled()) {
            return false;
        }
 
        $data = $this->libraryThing->download($isbn);
 
        if ($data === null) {
            return false;
        }
 
        try {
            $book->setCover($this->covers->store($data));
        } catch (CoverProcessingException) {
            // LibraryThing may send a format that cannot be stored, such as GIF.
            return false;
        }
 
        $this->entityManager->flush();
 
        return true;
    }
 
    /**
     * Stores a new cover and drops the previous one; null only removes it.
     *
     * @throws CoverProcessingException
     */
    public function changeCover(Book $book, ?UploadedFile $file): void {
        $previous = $book->getCover();
        $book->setCover($file ? $this->covers->store($file->getContent()) : null);
        $this->entityManager->flush();
 
        if ($previous !== null) {
            $this->covers->delete($previous);
        }
    }
 
    public function lend(Book $book, string $who): void {
        $book->lendTo($who, new \DateTimeImmutable());
        $this->entityManager->flush();
    }
 
    public function giveBack(Book $book): void {
        $book->giveBack(new \DateTimeImmutable());
        $this->entityManager->flush();
    }
 
    /**
     * Moves a book to the ousted collection with its labels and loan history.
     */
    public function oust(Book $book, string $reason): OustedBook {
        if ($book->isBorrowed()) {
            throw new \LogicException(\sprintf('Book %d must be returned before it is ousted.', $book->getId()));
        }
 
        $ousted = OustedBook::fromBook($book, $reason);
        foreach ($book->getBorrowHistory() as $entry) {
            $ousted->addBorrowHistory($entry->getWho(), $entry->getAt(), $entry->getTill());
        }
 
        $this->entityManager->wrapInTransaction(function () use ($book, $ousted): void {
            $this->entityManager->remove($book);
            $this->entityManager->flush();
            $this->entityManager->persist($ousted);
        });
 
        return $ousted;
    }
 
    /**
     * Ousts all the given books, or none of them if one fails.
     *
     * @param iterable<Book> $books
     * @return int how many books were ousted
     */
    public function oustAll(iterable $books, string $reason): int {
        return $this->entityManager->wrapInTransaction(function () use ($books, $reason): int {
            $count = 0;
            foreach ($books as $book) {
                $this->oust($book, $reason);
                $count++;
            }
            $this->entityManager->flush();
 
            return $count;
        });
    }
 
    public function restore(OustedBook $ousted): Book {
        $book = Book::fromOusted($ousted);
        foreach ($ousted->getBorrowHistory() as $entry) {
            $book->addBorrowHistory($entry->getWho(), $entry->getAt(), $entry->getTill());
        }
 
        $this->entityManager->wrapInTransaction(function () use ($book, $ousted): void {
            $this->entityManager->remove($ousted);
            $this->entityManager->flush();
            $this->entityManager->persist($book);
        });
 
        return $book;
    }
}