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{ class Collection{
public string $Name; public string $Name;
public string $UrlName;
public string $Url; public string $Url;
public ?int $SequenceNumber = null; public ?int $SequenceNumber = null;
public ?string $Type = null; public ?string $Type = null;
public function __construct(string $name){ public function __construct(string $name){
$this->Name = $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{ public function GetSortedName(): string{

View file

@ -31,6 +31,8 @@ const DATABASE_DEFAULT_DATABASE = 'se';
const DATABASE_DEFAULT_HOST = 'localhost'; const DATABASE_DEFAULT_HOST = 'localhost';
const EBOOKS_PER_PAGE = 12; 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_HEIGHT = 350;
const ARTWORK_THUMBNAIL_WIDTH = 350; const ARTWORK_THUMBNAIL_WIDTH = 350;

View file

@ -11,7 +11,23 @@ use function Safe\preg_replace;
use function Safe\sprintf; use function Safe\sprintf;
use function Safe\shell_exec; 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{ class Ebook{
use Traits\Accessor;
public ?int $EbookId = null;
public string $WwwFilesystemPath; public string $WwwFilesystemPath;
public string $RepoFilesystemPath; public string $RepoFilesystemPath;
public string $Url; public string $Url;
@ -25,8 +41,8 @@ class Ebook{
public $GitCommits = []; public $GitCommits = [];
/** @var array<EbookTag> $Tags */ /** @var array<EbookTag> $Tags */
public $Tags = []; public $Tags = [];
/** @var array<string> $LocTags */ /** @var array<LocSubject> $LocSubjects */
public $LocTags = []; public $LocSubjects = [];
/** @var array<Collection> $Collections */ /** @var array<Collection> $Collections */
public $Collections = []; public $Collections = [];
public string $Identifier; public string $Identifier;
@ -72,8 +88,49 @@ class Ebook{
public ?string $TextSinglePageUrl; public ?string $TextSinglePageUrl;
public ?string $TextSinglePageSizeNumber = null; public ?string $TextSinglePageSizeNumber = null;
public ?string $TextSinglePageSizeUnit = null; public ?string $TextSinglePageSizeUnit = null;
public ?int $TextSinglePageByteCount = null;
/** @var ?array<string> $TocEntries */ /** @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 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 * @throws Exceptions\EbookNotFoundException
@ -130,11 +187,11 @@ class Ebook{
try{ try{
// PHP Safe throws an exception from filesize() if the file doesn't exist, but PHP still // PHP Safe throws an exception from filesize() if the file doesn't exist, but PHP still
// emits a warning. So, just silence the warning. // 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'; $sizes = 'BKMGTP';
$factor = intval(floor((strlen((string)$bytes) - 1) / 3)); $factor = intval(floor((strlen((string)$this->TextSinglePageByteCount) - 1) / 3));
try{ try{
$this->TextSinglePageSizeNumber = sprintf('%.1f', $bytes / pow(1024, $factor)); $this->TextSinglePageSizeNumber = sprintf('%.1f', $this->TextSinglePageByteCount / pow(1024, $factor));
} }
catch(\DivisionByZeroError){ catch(\DivisionByZeroError){
$this->TextSinglePageSizeNumber = '0'; $this->TextSinglePageSizeNumber = '0';
@ -250,7 +307,9 @@ class Ebook{
// Get SE tags // Get SE tags
foreach($xml->xpath('/package/metadata/meta[@property="se:subject"]') ?: [] as $tag){ 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; $includeToc = sizeof($xml->xpath('/package/metadata/meta[@property="se:is-a-collection"]') ?: []) > 0;
@ -285,8 +344,10 @@ class Ebook{
} }
// Get LoC tags // Get LoC tags
foreach($xml->xpath('/package/metadata/dc:subject') ?: [] as $tag){ foreach($xml->xpath('/package/metadata/dc:subject') ?: [] as $subject){
$this->LocTags[] = (string)$tag; $locSubject = new LocSubject();
$locSubject->Name = $subject;
$this->LocSubjects[] = $locSubject;
} }
// Figure out authors and contributors // Figure out authors and contributors
@ -519,6 +580,60 @@ class Ebook{
// METHODS // 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{ public function GetCollectionPosition(Collection $collection): ?int{
foreach($this->Collections as $c){ foreach($this->Collections as $c){
if($c->Name == $collection->Name){ if($c->Name == $collection->Name){
@ -549,8 +664,8 @@ class Ebook{
$searchString .= ' ' . $tag->Name; $searchString .= ' ' . $tag->Name;
} }
foreach($this->LocTags as $tag){ foreach($this->LocSubjects as $subject){
$searchString .= ' ' . $tag; $searchString .= ' ' . $subject->Name;
} }
if($this->TocEntries !== null){ if($this->TocEntries !== null){
@ -816,4 +931,274 @@ class Ebook{
return false; 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{ enum EbookSourceType: string{
case ProjectGutenberg; case ProjectGutenberg = 'project_gutenberg';
case ProjectGutenbergAustralia; case ProjectGutenbergAustralia = 'project_gutenberg_australia';
case ProjectGutenbergCanada; case ProjectGutenbergCanada = 'project_gutenberg_canada';
case InternetArchive; case InternetArchive = 'internet_archive';
case HathiTrust; case HathiTrust = 'hathi_trust';
case Wikisource; case Wikisource = 'wikisource';
case GoogleBooks; case GoogleBooks = 'google_books';
case FadedPage; case FadedPage = 'faded_page';
case Other; case Other = 'other';
} }

View file

@ -1,14 +1,15 @@
<? <?
class EbookTag extends Tag{ class EbookTag extends Tag{
public function __construct(string $name){
$this->Name = $name;
$this->UrlName = Formatter::MakeUrlSafe($this->Name);
$this->_Url = '/subjects/' . $this->UrlName;
}
// ******* // *******
// GETTERS // GETTERS
// ******* // *******
protected function GetUrlName(): string{
if($this->_UrlName === null){
$this->_UrlName = Formatter::MakeUrlSafe($this->Name);
}
return $this->_UrlName;
}
protected function GetUrl(): string{ protected function GetUrl(): string{
if($this->_Url === null){ if($this->_Url === null){
@ -17,4 +18,45 @@ class EbookTag extends Tag{
return $this->_Url; 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 * @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 $Url
* @property string $UrlName
*/ */
class Tag{ class Tag{
use Traits\Accessor; use Traits\Accessor;
public int $TagId; public int $TagId;
public string $Name; public string $Name;
public string $UrlName; protected ?string $_UrlName = null;
protected ?string $_Url = null; protected ?string $_Url = null;
} }