Initial code changes to insert/update Ebook records

This commit is contained in:
Mike Colagrosso 2024-04-20 23:21:48 -06:00 committed by Alex Cabal
parent 073f138c47
commit 63d411a2e6
10 changed files with 531 additions and 28 deletions

View file

@ -3,13 +3,15 @@ use function Safe\preg_replace;
class Collection{
public string $Name;
public string $UrlName;
public string $Url;
public ?int $SequenceNumber = null;
public ?string $Type = null;
public function __construct(string $name){
$this->Name = $name;
$this->Url = '/collections/' . Formatter::MakeUrlSafe($this->Name);
$this->UrlName = Formatter::MakeUrlSafe($this->Name);
$this->Url = '/collections/' . $this->UrlName;
}
public function GetSortedName(): string{

View file

@ -31,6 +31,8 @@ const DATABASE_DEFAULT_DATABASE = 'se';
const DATABASE_DEFAULT_HOST = 'localhost';
const EBOOKS_PER_PAGE = 12;
const EBOOKS_MAX_STRING_LENGTH = 250;
const EBOOK_SINGLE_PAGE_SIZE_WARNING = 3 *1024 * 1024; // 3145728
const ARTWORK_THUMBNAIL_HEIGHT = 350;
const ARTWORK_THUMBNAIL_WIDTH = 350;

View file

@ -11,7 +11,23 @@ use function Safe\preg_replace;
use function Safe\sprintf;
use function Safe\shell_exec;
/**
* @property array<GitCommit> $GitCommits
* @property array<EbookTag> $EbookTags
* @property array<LocSubject> $LocSubjects
* @property array<Collection> $Collections
* @property array<EbookSource> $Sources
* @property array<Contributor> $Authors
* @property array<Contributor> $Illustrators
* @property array<Contributor> $Translators
* @property array<Contributor> $Contributors
* @property ?array<string> $TocEntries
* @property string $IndexableText
*/
class Ebook{
use Traits\Accessor;
public ?int $EbookId = null;
public string $WwwFilesystemPath;
public string $RepoFilesystemPath;
public string $Url;
@ -25,8 +41,8 @@ class Ebook{
public $GitCommits = [];
/** @var array<EbookTag> $Tags */
public $Tags = [];
/** @var array<string> $LocTags */
public $LocTags = [];
/** @var array<LocSubject> $LocSubjects */
public $LocSubjects = [];
/** @var array<Collection> $Collections */
public $Collections = [];
public string $Identifier;
@ -72,8 +88,49 @@ class Ebook{
public ?string $TextSinglePageUrl;
public ?string $TextSinglePageSizeNumber = null;
public ?string $TextSinglePageSizeUnit = null;
public ?int $TextSinglePageByteCount = null;
/** @var ?array<string> $TocEntries */
public $TocEntries = null; // A list of non-Roman ToC entries ONLY IF the work has the 'se:is-a-collection' metadata element, null otherwise
protected ?string $_IndexableText = null;
// *******
// GETTERS
// *******
protected function GetIndexableText(): string{
if($this->_IndexableText === null){
$this->_IndexableText = $this->FullTitle ?? $this->Title;
$this->_IndexableText .= ' ' . $this->AlternateTitle;
foreach($this->Collections as $collection){
$this->_IndexableText .= ' ' . $collection->Name;
}
foreach($this->Authors as $author){
$this->_IndexableText .= ' ' . $author->Name;
}
foreach($this->Tags as $tag){
$this->_IndexableText .= ' ' . $tag->Name;
}
foreach($this->LocSubjects as $subject){
$this->_IndexableText .= ' ' . $subject->Name;
}
if($this->TocEntries !== null){
foreach($this->TocEntries as $item){
$this->_IndexableText .= ' ' . $item;
}
}
$this->_IndexableText .= ' ' . $this->Description;
$this->_IndexableText .= ' ' . $this->LongDescription;
}
return $this->_IndexableText;
}
/**
* @throws Exceptions\EbookNotFoundException
@ -130,11 +187,11 @@ class Ebook{
try{
// PHP Safe throws an exception from filesize() if the file doesn't exist, but PHP still
// emits a warning. So, just silence the warning.
$bytes = @filesize($this->WwwFilesystemPath . '/text/single-page.xhtml');
$this->TextSinglePageByteCount = @filesize($this->WwwFilesystemPath . '/text/single-page.xhtml');
$sizes = 'BKMGTP';
$factor = intval(floor((strlen((string)$bytes) - 1) / 3));
$factor = intval(floor((strlen((string)$this->TextSinglePageByteCount) - 1) / 3));
try{
$this->TextSinglePageSizeNumber = sprintf('%.1f', $bytes / pow(1024, $factor));
$this->TextSinglePageSizeNumber = sprintf('%.1f', $this->TextSinglePageByteCount / pow(1024, $factor));
}
catch(\DivisionByZeroError){
$this->TextSinglePageSizeNumber = '0';
@ -250,7 +307,9 @@ class Ebook{
// Get SE tags
foreach($xml->xpath('/package/metadata/meta[@property="se:subject"]') ?: [] as $tag){
$this->Tags[] = new EbookTag($tag);
$ebookTag = new EbookTag();
$ebookTag->Name = $tag;
$this->Tags[] = $ebookTag;
}
$includeToc = sizeof($xml->xpath('/package/metadata/meta[@property="se:is-a-collection"]') ?: []) > 0;
@ -285,8 +344,10 @@ class Ebook{
}
// Get LoC tags
foreach($xml->xpath('/package/metadata/dc:subject') ?: [] as $tag){
$this->LocTags[] = (string)$tag;
foreach($xml->xpath('/package/metadata/dc:subject') ?: [] as $subject){
$locSubject = new LocSubject();
$locSubject->Name = $subject;
$this->LocSubjects[] = $locSubject;
}
// Figure out authors and contributors
@ -519,6 +580,60 @@ class Ebook{
// METHODS
// *******
public function Validate(): void{
$error = new Exceptions\ValidationException();
if($this->Identifier === null || $this->Identifier == ''){
$error->Add(new Exceptions\EbookIdentifierRequiredException());
}
if($this->Identifier !== null && strlen($this->Identifier) > EBOOKS_MAX_STRING_LENGTH){
$error->Add(new Exceptions\StringTooLongException('Ebook Identifier'));
}
if($this->Title === null || $this->Title == ''){
$error->Add(new Exceptions\EbookTitleRequiredException());
}
if($this->Title !== null && strlen($this->Title) > EBOOKS_MAX_STRING_LENGTH){
$error->Add(new Exceptions\StringTooLongException('Ebook Title'));
}
// TODO: Add more validation.
if($error->HasExceptions){
throw $error;
}
}
public function CreateOrUpdate(): void{
$existingEbook = Library::GetEbookByIdentifier($this->Identifier);
if($existingEbook === null){
$this->Create();
return;
}
$this->EbookId = $existingEbook->EbookId;
$this->Save();
}
private function InsertTagStrings(): void{
$tags = [];
foreach($this->Tags as $ebookTag){
$tags[] = EbookTag::GetOrCreate($ebookTag);
}
$this->Tags = $tags;
}
private function InsertLocSubjectStrings(): void{
$subjects = [];
foreach($this->LocSubjects as $locSubject){
$subjects[] = LocSubject::GetOrCreate($locSubject);
}
$this->LocSubjects = $subjects;
}
public function GetCollectionPosition(Collection $collection): ?int{
foreach($this->Collections as $c){
if($c->Name == $collection->Name){
@ -549,8 +664,8 @@ class Ebook{
$searchString .= ' ' . $tag->Name;
}
foreach($this->LocTags as $tag){
$searchString .= ' ' . $tag;
foreach($this->LocSubjects as $subject){
$searchString .= ' ' . $subject->Name;
}
if($this->TocEntries !== null){
@ -816,4 +931,274 @@ class Ebook{
return false;
}
// ***********
// ORM METHODS
// ***********
public function Create(): void{
$this->Validate();
$this->InsertTagStrings();
$this->InsertLocSubjectStrings();
Db::Query('
INSERT into Ebooks (Identifier, WwwFilesystemPath, RepoFilesystemPath, KindleCoverUrl, EpubUrl,
AdvancedEpubUrl, KepubUrl, Azw3Url, DistCoverUrl, Title, FullTitle, AlternateTitle,
Description, LongDescription, Language, WordCount, ReadingEase, GitHubUrl, WikipediaUrl,
EbookCreated, EbookUpdated, TextSinglePageByteCount, IndexableText)
values (?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?)
', [$this->Identifier, $this->WwwFilesystemPath, $this->RepoFilesystemPath, $this->KindleCoverUrl, $this->EpubUrl,
$this->AdvancedEpubUrl, $this->KepubUrl, $this->Azw3Url, $this->DistCoverUrl, $this->Title,
$this->FullTitle, $this->AlternateTitle, $this->Description, $this->LongDescription,
$this->Language, $this->WordCount, $this->ReadingEase, $this->GitHubUrl, $this->WikipediaUrl,
$this->Created, $this->Updated, $this->TextSinglePageByteCount, $this->IndexableText]);
$this->EbookId = Db::GetLastInsertedId();
$this->InsertTags();
$this->InsertLocSubjects();
$this->InsertGitCommits();
$this->InsertCollections();
$this->InsertSources();
$this->InsertContributors();
$this->InsertTocEntries();
}
public function Save(): void{
$this->Validate();
$this->InsertTagStrings();
$this->InsertLocSubjectStrings();
Db::Query('
UPDATE Ebooks
set
Identifier = ?,
WwwFilesystemPath = ?,
RepoFilesystemPath = ?,
KindleCoverUrl = ?,
EpubUrl = ?,
AdvancedEpubUrl = ?,
KepubUrl = ?,
Azw3Url = ?,
DistCoverUrl = ?,
Title = ?,
FullTitle = ?,
AlternateTitle = ?,
Description = ?,
LongDescription = ?,
Language = ?,
WordCount = ?,
ReadingEase = ?,
GitHubUrl = ?,
WikipediaUrl = ?,
EbookCreated = ?,
EbookUpdated = ?,
TextSinglePageByteCount = ?,
IndexableText = ?
where
EbookId = ?
', [$this->Identifier, $this->WwwFilesystemPath, $this->RepoFilesystemPath, $this->KindleCoverUrl, $this->EpubUrl,
$this->AdvancedEpubUrl, $this->KepubUrl, $this->Azw3Url, $this->DistCoverUrl, $this->Title,
$this->FullTitle, $this->AlternateTitle, $this->Description, $this->LongDescription,
$this->Language, $this->WordCount, $this->ReadingEase, $this->GitHubUrl, $this->WikipediaUrl,
$this->Created, $this->Updated, $this->TextSinglePageByteCount, $this->IndexableText,
$this->EbookId]);
$this->DeleteTags();
$this->InsertTags();
$this->DeleteLocSubjects();
$this->InsertLocSubjects();
$this->DeleteGitCommits();
$this->InsertGitCommits();
$this->DeleteCollections();
$this->InsertCollections();
$this->DeleteSources();
$this->InsertSources();
$this->DeleteContributors();
$this->InsertContributors();
$this->DeleteTocEntries();
$this->InsertTocEntries();
}
private function DeleteTags(): void{
Db::Query('
DELETE from EbookTags
where
EbookId = ?
', [$this->EbookId]
);
}
private function InsertTags(): void{
foreach($this->Tags as $tag){
Db::Query('
INSERT into EbookTags (EbookId, TagId)
values (?,
?)
', [$this->EbookId, $tag->TagId]);
}
}
private function DeleteLocSubjects(): void{
Db::Query('
DELETE from EbookLocSubjects
where
EbookId = ?
', [$this->EbookId]
);
}
private function InsertLocSubjects(): void{
foreach($this->LocSubjects as $locSubject){
Db::Query('
INSERT into EbookLocSubjects (EbookId, LocSubjectId)
values (?,
?)
', [$this->EbookId, $locSubject->LocSubjectId]);
}
}
private function DeleteGitCommits(): void{
Db::Query('
DELETE from GitCommits
where
EbookId = ?
', [$this->EbookId]
);
}
private function InsertGitCommits(): void{
foreach($this->GitCommits as $commit){
Db::Query('
INSERT into GitCommits (EbookId, Created, Message, Hash)
values (?,
?,
?,
?)
', [$this->EbookId, $commit->Created, $commit->Message, $commit->Hash]);
}
}
private function DeleteCollections(): void{
Db::Query('
DELETE from Collections
where
EbookId = ?
', [$this->EbookId]
);
}
private function InsertCollections(): void{
foreach($this->Collections as $collection){
Db::Query('
INSERT into Collections (EbookId, Name, UrlName, SequenceNumber, Type)
values (?,
?,
?,
?,
?)
', [$this->EbookId, $collection->Name, $collection->UrlName, $collection->SequenceNumber, $collection->Type]);
}
}
private function DeleteSources(): void{
Db::Query('
DELETE from EbookSources
where
EbookId = ?
', [$this->EbookId]
);
}
private function InsertSources(): void{
foreach($this->Sources as $source){
Db::Query('
INSERT into EbookSources (EbookId, Type, Url)
values (?,
?,
?)
', [$this->EbookId, $source->Type->value, $source->Url]);
}
}
private function DeleteContributors(): void{
Db::Query('
DELETE from Contributors
where
EbookId = ?
', [$this->EbookId]
);
}
private function InsertContributors(): void{
$allContributors = array_merge($this->Authors, $this->Illustrators, $this->Translators, $this->Contributors);
foreach($allContributors as $sortOrder => $contributor){
Db::Query('
INSERT into Contributors (EbookId, Name, UrlName, SortName, WikipediaUrl, MarcRole, FullName,
NacoafUrl, SortOrder)
values (?,
?,
?,
?,
?,
?,
?,
?,
?)
', [$this->EbookId, $contributor->Name, $contributor->UrlName, $contributor->SortName,
$contributor->WikipediaUrl, $contributor->MarcRole, $contributor->FullName,
$contributor->NacoafUrl, $sortOrder]);
}
}
private function DeleteTocEntries(): void{
Db::Query('
DELETE from TocEntries
where
EbookId = ?
', [$this->EbookId]
);
}
private function InsertTocEntries(): void{
if($this->TocEntries !== null){
foreach($this->TocEntries as $tocEntry){
Db::Query('
INSERT into TocEntries (EbookId, TocEntry)
values (?,
?)
', [$this->EbookId, $tocEntry]);
}
}
}
}

View file

@ -1,12 +1,12 @@
<?
enum EbookSourceType{
case ProjectGutenberg;
case ProjectGutenbergAustralia;
case ProjectGutenbergCanada;
case InternetArchive;
case HathiTrust;
case Wikisource;
case GoogleBooks;
case FadedPage;
case Other;
enum EbookSourceType: string{
case ProjectGutenberg = 'project_gutenberg';
case ProjectGutenbergAustralia = 'project_gutenberg_australia';
case ProjectGutenbergCanada = 'project_gutenberg_canada';
case InternetArchive = 'internet_archive';
case HathiTrust = 'hathi_trust';
case Wikisource = 'wikisource';
case GoogleBooks = 'google_books';
case FadedPage = 'faded_page';
case Other = 'other';
}

View file

@ -1,14 +1,15 @@
<?
class EbookTag extends Tag{
public function __construct(string $name){
$this->Name = $name;
$this->UrlName = Formatter::MakeUrlSafe($this->Name);
$this->_Url = '/subjects/' . $this->UrlName;
}
// *******
// GETTERS
// *******
protected function GetUrlName(): string{
if($this->_UrlName === null){
$this->_UrlName = Formatter::MakeUrlSafe($this->Name);
}
return $this->_UrlName;
}
protected function GetUrl(): string{
if($this->_Url === null){
@ -17,4 +18,45 @@ class EbookTag extends Tag{
return $this->_Url;
}
// *******
// METHODS
// *******
public function Validate(): void{
$error = new Exceptions\ValidationException();
if(strlen($this->Name) > EBOOKS_MAX_STRING_LENGTH){
$error->Add(new Exceptions\StringTooLongException('Ebook tag: '. $this->Name));
}
if($error->HasExceptions){
throw $error;
}
}
public function Create(): void{
$this->Validate();
Db::Query('
INSERT into Tags (Name)
values (?)
', [$this->Name]);
$this->TagId = Db::GetLastInsertedId();
}
public static function GetOrCreate(EbookTag $ebookTag): EbookTag{
$result = Db::Query('
SELECT *
from Tags
where Name = ?
', [$ebookTag->Name], 'EbookTag');
if(isset($result[0])){
return $result[0];
}
else{
$ebookTag->Create();
return $ebookTag;
}
}
}

View file

@ -0,0 +1,5 @@
<?
namespace Exceptions;
class EbookIdentifierRequiredException extends AppException{
}

View file

@ -0,0 +1,5 @@
<?
namespace Exceptions;
class EbookTitleRequiredException extends AppException{
}

View file

@ -673,6 +673,24 @@ class Library{
}
}
public static function GetEbookByIdentifier(?string $identifier): ?Ebook{
if($identifier === null){
return null;
}
$result = Db::Query('
SELECT *
from Ebooks
where Identifier = ?
', [$identifier], 'Ebook');
if(sizeof($result) == 0){
return null;
}
return $result[0];
}
/**
* @throws Exceptions\AppException
*/

43
lib/LocSubject.php Normal file
View file

@ -0,0 +1,43 @@
<?
class LocSubject extends Tag{
public int $LocSubjectId;
public string $Name;
public function Validate(): void{
$error = new Exceptions\ValidationException();
if(strlen($this->Name) > EBOOKS_MAX_STRING_LENGTH){
$error->Add(new Exceptions\StringTooLongException('LoC subject: '. $this->Name));
}
if($error->HasExceptions){
throw $error;
}
}
public function Create(): void{
$this->Validate();
Db::Query('
INSERT into LocSubjects (Name)
values (?)
', [$this->Name]);
$this->LocSubjectId = Db::GetLastInsertedId();
}
public static function GetOrCreate(LocSubject $locSubject): LocSubject{
$result = Db::Query('
SELECT *
from LocSubjects
where Name = ?
', [$locSubject->Name], 'LocSubject');
if(isset($result[0])){
return $result[0];
}
else{
$locSubject->Create();
return $locSubject;
}
}
}

View file

@ -1,12 +1,13 @@
<?
/**
* @property string $Url
* @property string $UrlName
*/
class Tag{
use Traits\Accessor;
public int $TagId;
public string $Name;
public string $UrlName;
protected ?string $_UrlName = null;
protected ?string $_Url = null;
}