mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-04 09:50:42 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48f232790a | |||
| 3c55aa5f43 | |||
| 8c1edb30a6 | |||
| 5e64af4448 | |||
| 9f60017cfe | |||
| b6a86d11d2 | |||
| db86bfd63d | |||
| 7ff72a8920 | |||
| 2c4f86d148 |
@@ -108,9 +108,9 @@ export default {
|
|||||||
if (res.warning) {
|
if (res.warning) {
|
||||||
this.$toast.warning(res.warning)
|
this.$toast.warning(res.warning)
|
||||||
} else if (res.updated) {
|
} else if (res.updated) {
|
||||||
this.$toast.success(this.$strings.ToastNoUpdatesNecessary)
|
this.$toast.success(this.$strings.ToastItemDetailsUpdateSuccess)
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info(this.$strings.ToastItemDetailsUpdateUnneeded)
|
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -170,7 +170,7 @@ export default {
|
|||||||
this.isProcessing = false
|
this.isProcessing = false
|
||||||
if (updateResult) {
|
if (updateResult) {
|
||||||
if (updateResult.updated) {
|
if (updateResult.updated) {
|
||||||
this.$toast.success(this.$strings.MessageItemDetailsUpdated)
|
this.$toast.success(this.$strings.ToastItemDetailsUpdateSuccess)
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.13.1",
|
"version": "2.13.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.13.1",
|
"version": "2.13.2",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.13.1",
|
"version": "2.13.2",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
+243
-1
@@ -204,5 +204,247 @@
|
|||||||
"HeaderYourStats": "Tvoja statistika",
|
"HeaderYourStats": "Tvoja statistika",
|
||||||
"LabelAbridged": "Skrajšano",
|
"LabelAbridged": "Skrajšano",
|
||||||
"LabelAbridgedChecked": "Skrajšano (omogočeno)",
|
"LabelAbridgedChecked": "Skrajšano (omogočeno)",
|
||||||
"LabelAbridgedUnchecked": "Neskrajšano (onemogočeno)"
|
"LabelAbridgedUnchecked": "Neskrajšano (onemogočeno)",
|
||||||
|
"LabelAccessibleBy": "Dostopno iz",
|
||||||
|
"LabelAccountType": "Vrsta računa",
|
||||||
|
"LabelAccountTypeAdmin": "Administrator",
|
||||||
|
"LabelAccountTypeGuest": "Gost",
|
||||||
|
"LabelAccountTypeUser": "Uporabnik",
|
||||||
|
"LabelActivity": "Aktivnost",
|
||||||
|
"LabelAddToCollection": "Dodaj v zbirko",
|
||||||
|
"LabelAddToCollectionBatch": "Dodaj {0} knjig v zbirko",
|
||||||
|
"LabelAddToPlaylist": "Dodaj na seznam predvajanja",
|
||||||
|
"LabelAddToPlaylistBatch": "Dodaj {0} elementov v seznam predvajanja",
|
||||||
|
"LabelAddedAt": "Dodano ob",
|
||||||
|
"LabelAddedDate": "Dodano {0}",
|
||||||
|
"LabelAdminUsersOnly": "Samo administratorji",
|
||||||
|
"LabelAll": "Vsi",
|
||||||
|
"LabelAllUsers": "Vsi uporabniki",
|
||||||
|
"LabelAllUsersExcludingGuests": "Vsi uporabniki razen gosti",
|
||||||
|
"LabelAllUsersIncludingGuests": "Vsi uporabniki vključno z gosti",
|
||||||
|
"LabelAlreadyInYourLibrary": "Že v tvoji knjižnici",
|
||||||
|
"LabelAppend": "Priloži",
|
||||||
|
"LabelAuthor": "Avtor",
|
||||||
|
"LabelAuthorFirstLast": "Avtor (ime priimek)",
|
||||||
|
"LabelAuthorLastFirst": "Avtor (priimek, ime)",
|
||||||
|
"LabelAuthors": "Avtorji",
|
||||||
|
"LabelAutoDownloadEpisodes": "Samodejni prenos epizod",
|
||||||
|
"LabelAutoFetchMetadata": "Samodejno pridobivanje metapodatkov",
|
||||||
|
"LabelAutoFetchMetadataHelp": "Pridobi metapodatke za naslov, avtorja in serijo za poenostavitev nalaganja. Po nalaganju bo morda treba ujemanje dodatnih metapodatkov.",
|
||||||
|
"LabelAutoLaunch": "Samodejni zagon",
|
||||||
|
"LabelAutoLaunchDescription": "Samodejna preusmeritev na ponudnika avtentikacije ob navigaciji na prijavno stran (ročna preglasitev poti <code>/login?autoLaunch=0</code>)",
|
||||||
|
"LabelAutoRegister": "Samodejna registracija",
|
||||||
|
"LabelAutoRegisterDescription": "Po prijavi samodejno ustvari nove uporabnike",
|
||||||
|
"LabelBackToUser": "Nazaj na uporabnika",
|
||||||
|
"LabelBackupLocation": "Lokacija rezervne kopije",
|
||||||
|
"LabelBackupsEnableAutomaticBackups": "Omogoči samodejno varnostno kopiranje",
|
||||||
|
"LabelBackupsEnableAutomaticBackupsHelp": "Varnostne kopije shranjene v /metadata/backups",
|
||||||
|
"LabelBackupsMaxBackupSize": "Največja velikost varnostne kopije (v GB) (0 za neomejeno)",
|
||||||
|
"LabelBackupsMaxBackupSizeHelp": "Kot zaščita pred napačno konfiguracijo, varnostne kopije ne bodo uspele, če presežejo konfigurirano velikost.",
|
||||||
|
"LabelBackupsNumberToKeep": "Število varnostnih kopij, ki jih je treba hraniti",
|
||||||
|
"LabelBackupsNumberToKeepHelp": "Naenkrat bo odstranjena samo ena varnostna kopija, če že imate več varnostnih kopij, jih odstranite ročno.",
|
||||||
|
"LabelBitrate": "Bitna hitrost",
|
||||||
|
"LabelBooks": "Knjige",
|
||||||
|
"LabelButtonText": "Besedilo gumba",
|
||||||
|
"LabelByAuthor": "od {0}",
|
||||||
|
"LabelChangePassword": "Spremeni geslo",
|
||||||
|
"LabelChannels": "Kanali",
|
||||||
|
"LabelChapterTitle": "Naslov poglavja",
|
||||||
|
"LabelChapters": "Poglavja",
|
||||||
|
"LabelChaptersFound": "najdenih poglavij",
|
||||||
|
"LabelClickForMoreInfo": "Klikni za več informacij",
|
||||||
|
"LabelClosePlayer": "Zapri predvajalnik",
|
||||||
|
"LabelCodec": "Kodek",
|
||||||
|
"LabelCollapseSeries": "Strni serije",
|
||||||
|
"LabelCollapseSubSeries": "Strni podserije",
|
||||||
|
"LabelCollection": "Zbirka",
|
||||||
|
"LabelCollections": "Zbirke",
|
||||||
|
"LabelComplete": "Končano",
|
||||||
|
"LabelConfirmPassword": "Potrdi geslo",
|
||||||
|
"LabelContinueListening": "Nadaljuj poslušanje",
|
||||||
|
"LabelContinueReading": "Nadaljuj branje",
|
||||||
|
"LabelContinueSeries": "Nadaljuj s serijo",
|
||||||
|
"LabelCover": "Naslovnica",
|
||||||
|
"LabelCoverImageURL": "URL naslovne slike",
|
||||||
|
"LabelCreatedAt": "Ustvarjeno ob",
|
||||||
|
"LabelCronExpression": "Cron izraz",
|
||||||
|
"LabelCurrent": "Trenutno",
|
||||||
|
"LabelCurrently": "Trenutno:",
|
||||||
|
"LabelCustomCronExpression": "Cron izraz po meri:",
|
||||||
|
"LabelDatetime": "Datum in ura",
|
||||||
|
"LabelDays": "Dnevi",
|
||||||
|
"LabelDeleteFromFileSystemCheckbox": "Izbriši iz datotečnega sistema (počisti polje, če želiš odstraniti samo iz zbirke podatkov)",
|
||||||
|
"LabelDescription": "Opis",
|
||||||
|
"LabelDeselectAll": "Odznači vse",
|
||||||
|
"LabelDevice": "Naprava",
|
||||||
|
"LabelDeviceInfo": "Podatki o napravi",
|
||||||
|
"LabelDeviceIsAvailableTo": "Naprava je na voljo za...",
|
||||||
|
"LabelDirectory": "Imenik",
|
||||||
|
"LabelDiscFromFilename": "Disk iz imena datoteke",
|
||||||
|
"LabelDiscFromMetadata": "Disk iz metapodatkov",
|
||||||
|
"LabelDiscover": "Odkrij",
|
||||||
|
"LabelDownload": "Prenos",
|
||||||
|
"LabelDownloadNEpisodes": "Prenesi {0} epizod",
|
||||||
|
"LabelDuration": "Trajanje",
|
||||||
|
"LabelDurationComparisonExactMatch": "(natančno ujemanje)",
|
||||||
|
"LabelDurationComparisonLonger": "({0} dlje)",
|
||||||
|
"LabelDurationComparisonShorter": "({0} krajše)",
|
||||||
|
"LabelDurationFound": "Najdeno trajanje:",
|
||||||
|
"LabelEbook": "Eknjiga",
|
||||||
|
"LabelEbooks": "Eknjige",
|
||||||
|
"LabelEdit": "Uredi",
|
||||||
|
"LabelEmail": "E-pošta",
|
||||||
|
"LabelEmailSettingsFromAddress": "Iz naslova",
|
||||||
|
"LabelEmailSettingsRejectUnauthorized": "Zavrni nepooblaščena potrdila",
|
||||||
|
"LabelEmailSettingsRejectUnauthorizedHelp": "Če onemogočite preverjanje veljavnosti potrdila SSL, lahko izpostavite svojo povezavo varnostnim tveganjem, kot so napadi človek v sredini. To možnost onemogočite le, če razumete posledice in zaupate poštnemu strežniku, s katerim se povezujete.",
|
||||||
|
"LabelEmailSettingsSecure": "Varno",
|
||||||
|
"LabelEmailSettingsSecureHelp": "Če je omogočeno, bo povezava pri povezovanju s strežnikom uporabljala TLS. Če je onemogočeno, se TLS uporablja, če strežnik podpira razširitev STARTTLS. V večini primerov nastavite to vrednost na omogočeno, če se povezujete z vrati 465. Za vrata 587 ali 25 naj ostane onemogočeno. (iz nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Testiraj naslov",
|
||||||
|
"LabelEmbeddedCover": "Vdelana naslovnica",
|
||||||
|
"LabelEnable": "Omogoči",
|
||||||
|
"LabelEnd": "Konec",
|
||||||
|
"LabelEndOfChapter": "Konec poglavja",
|
||||||
|
"LabelEpisode": "Epizoda",
|
||||||
|
"LabelEpisodeTitle": "Naslov epizode",
|
||||||
|
"LabelEpisodeType": "Tip epizode",
|
||||||
|
"LabelEpisodes": "Epizode",
|
||||||
|
"LabelExample": "Primer",
|
||||||
|
"LabelExpandSeries": "Razširi serije",
|
||||||
|
"LabelExpandSubSeries": "Razširi podserije",
|
||||||
|
"LabelExplicit": "Eksplicitno",
|
||||||
|
"LabelExplicitChecked": "Eksplicitno (omogočeno)",
|
||||||
|
"LabelExplicitUnchecked": "Ne eksplicitno (onemogočeno)",
|
||||||
|
"LabelExportOPML": "Izvozi OPML",
|
||||||
|
"LabelFeedURL": "URL vir",
|
||||||
|
"LabelFetchingMetadata": "Pridobivam metapodatke",
|
||||||
|
"LabelFile": "Datoteka",
|
||||||
|
"LabelFileBirthtime": "Čas ustvarjanja datoteke",
|
||||||
|
"LabelFileBornDate": "Ustvarjena {0}",
|
||||||
|
"LabelFileModified": "Datoteke spremenjena",
|
||||||
|
"LabelFileModifiedDate": "Spremenjena {0}",
|
||||||
|
"LabelFilename": "Ime datoteke",
|
||||||
|
"LabelFilterByUser": "Filtriraj po uporabniku",
|
||||||
|
"LabelFindEpisodes": "Poišči epizode",
|
||||||
|
"LabelFinished": "Zaključeno",
|
||||||
|
"LabelFolder": "Mapa",
|
||||||
|
"LabelFolders": "Mape",
|
||||||
|
"LabelFontBold": "Krepko",
|
||||||
|
"LabelFontBoldness": "Krepkost pisave",
|
||||||
|
"LabelFontFamily": "Družina pisave",
|
||||||
|
"LabelFontItalic": "Ležeče",
|
||||||
|
"LabelFontScale": "Merilo pisave",
|
||||||
|
"LabelFontStrikethrough": "Prečrtano",
|
||||||
|
"LabelFormat": "Oblika",
|
||||||
|
"LabelGenre": "Žanr",
|
||||||
|
"LabelGenres": "Žanri",
|
||||||
|
"LabelHardDeleteFile": "Trdo brisanje datoteke",
|
||||||
|
"LabelHasEbook": "Ima eknjigo",
|
||||||
|
"LabelHasSupplementaryEbook": "Ima dodatno eknjigo",
|
||||||
|
"LabelHideSubtitles": "Skrij podnapise",
|
||||||
|
"LabelHighestPriority": "Najvišja prioriteta",
|
||||||
|
"LabelHost": "Gostitelj",
|
||||||
|
"LabelHour": "Ura",
|
||||||
|
"LabelHours": "Ure",
|
||||||
|
"LabelIcon": "Ikona",
|
||||||
|
"LabelImageURLFromTheWeb": "URL slike iz spleta",
|
||||||
|
"LabelInProgress": "V teku",
|
||||||
|
"LabelIncludeInTracklist": "Vključi v seznam skladb",
|
||||||
|
"LabelIncomplete": "Nepopolno",
|
||||||
|
"LabelInterval": "Interval",
|
||||||
|
"LabelIntervalCustomDailyWeekly": "Dnevno/tedensko po meri",
|
||||||
|
"LabelIntervalEvery12Hours": "Vsakih 12 ur",
|
||||||
|
"LabelIntervalEvery15Minutes": "Vsakih 15 minut",
|
||||||
|
"LabelIntervalEvery2Hours": "Vsake 2 uri",
|
||||||
|
"LabelIntervalEvery30Minutes": "Vsakih 30 minut",
|
||||||
|
"LabelIntervalEvery6Hours": "Vsakih 6 ur",
|
||||||
|
"LabelIntervalEveryDay": "Vsak dan",
|
||||||
|
"LabelIntervalEveryHour": "Vsako uro",
|
||||||
|
"LabelInvert": "Obrni izbor",
|
||||||
|
"LabelItem": "Element",
|
||||||
|
"LabelJumpBackwardAmount": "Količina skoka nazaj",
|
||||||
|
"LabelJumpForwardAmount": "Količina skoka naprej",
|
||||||
|
"LabelLanguage": "Jezik",
|
||||||
|
"LabelLanguageDefaultServer": "Privzeti jezik strežnika",
|
||||||
|
"LabelLanguages": "Jeziki",
|
||||||
|
"LabelLastBookAdded": "Zadnja dodana knjiga",
|
||||||
|
"LabelLastBookUpdated": "Zadnja posodobljena knjiga",
|
||||||
|
"LabelLastSeen": "Nazadnje viden",
|
||||||
|
"LabelLastTime": "Zadnji čas",
|
||||||
|
"LabelLastUpdate": "Zadnja posodobitev",
|
||||||
|
"LabelLayout": "Postavitev",
|
||||||
|
"LabelLayoutSinglePage": "Ena stran",
|
||||||
|
"LabelLayoutSplitPage": "Razdeli stran",
|
||||||
|
"LabelLess": "Manj",
|
||||||
|
"LabelLibrariesAccessibleToUser": "Knjižnice, dostopne uporabniku",
|
||||||
|
"LabelLibrary": "Knjižnica",
|
||||||
|
"LabelLibraryFilterSublistEmpty": "Ne {0}",
|
||||||
|
"LabelLibraryItem": "Element knjižnice",
|
||||||
|
"LabelLibraryName": "Ime knjižnice",
|
||||||
|
"LabelLimit": "Omejitev",
|
||||||
|
"LabelLineSpacing": "Razmik med vrsticami",
|
||||||
|
"LabelListenAgain": "Poslušaj znova",
|
||||||
|
"LabelLogLevelDebug": "Odpravljanje napak",
|
||||||
|
"LabelLogLevelInfo": "Info",
|
||||||
|
"LabelLogLevelWarn": "Opozoritve",
|
||||||
|
"LabelLookForNewEpisodesAfterDate": "Poiščite nove epizode po tem datumu",
|
||||||
|
"LabelLowestPriority": "Najnižja prioriteta",
|
||||||
|
"LabelMatchExistingUsersBy": "Poveži obstoječe uporabnike po",
|
||||||
|
"LabelMatchExistingUsersByDescription": "Uporablja se za povezovanje obstoječih uporabnikov. Ko se vzpostavi povezava, se bodo uporabniki ujemali z enoličnim ID-jem vašega ponudnika SSO",
|
||||||
|
"LabelMediaPlayer": "Medijski predvajalnik",
|
||||||
|
"LabelMediaType": "Vrsta medija",
|
||||||
|
"LabelMetaTag": "Meta oznaka",
|
||||||
|
"LabelMetaTags": "Meta oznake",
|
||||||
|
"LabelMetadataOrderOfPrecedenceDescription": "Viri metapodatkov višje prioritete bodo preglasili vire metapodatkov nižje prioritete",
|
||||||
|
"LabelMetadataProvider": "Ponudnik metapodatkov",
|
||||||
|
"LabelMinute": "Minuta",
|
||||||
|
"LabelMinutes": "Minute",
|
||||||
|
"LabelMissing": "Manjkajoče",
|
||||||
|
"LabelMissingEbook": "Nima nobene eknjige",
|
||||||
|
"LabelMissingSupplementaryEbook": "Nima nobene dodatne eknjige",
|
||||||
|
"LabelMobileRedirectURIs": "Dovoljeni mobilni preusmeritveni URI-ji",
|
||||||
|
"LabelMobileRedirectURIsDescription": "To je seznam dovoljenih veljavnih preusmeritvenih URI-jev za mobilne aplikacije. Privzeti je <code>audiobookshelf://oauth</code>, ki ga lahko odstranite ali dopolnite z dodatnimi URI-ji za integracijo aplikacij tretjih oseb. Uporaba zvezdice (<code>*</code>) kot edinega vnosa dovoljuje kateri koli URI.",
|
||||||
|
"LabelMore": "Več",
|
||||||
|
"LabelMoreInfo": "Več informacij",
|
||||||
|
"LabelName": "Naziv",
|
||||||
|
"LabelNarrator": "Pripovedovalec",
|
||||||
|
"LabelNarrators": "Pripovedovalci",
|
||||||
|
"LabelNew": "Novo",
|
||||||
|
"LabelNewPassword": "Novo geslo",
|
||||||
|
"LabelNewestAuthors": "Najnovejši avtorji",
|
||||||
|
"LabelNewestEpisodes": "Najnovejše epizode",
|
||||||
|
"LabelNextBackupDate": "Naslednji datum varnostnega kopiranja",
|
||||||
|
"LabelNextScheduledRun": "Naslednji načrtovani zagon",
|
||||||
|
"LabelNoCustomMetadataProviders": "Ni ponudnikov metapodatkov po meri",
|
||||||
|
"LabelNoEpisodesSelected": "Izbrana ni nobena epizoda",
|
||||||
|
"LabelNotFinished": "Ni dokončano",
|
||||||
|
"LabelNotStarted": "Ni zagnano",
|
||||||
|
"LabelNotes": "Opombe",
|
||||||
|
"LabelNotificationAppriseURL": "Apprise URL(ji)",
|
||||||
|
"LabelNotificationAvailableVariables": "Razpoložljive spremenljivke",
|
||||||
|
"LabelNotificationBodyTemplate": "Predloga telesa",
|
||||||
|
"LabelNotificationEvent": "Dogodek obvestila",
|
||||||
|
"LabelNotificationTitleTemplate": "Predloga naslova",
|
||||||
|
"LabelNotificationsMaxFailedAttempts": "Najvišje število neuspelih poskusov",
|
||||||
|
"LabelNotificationsMaxFailedAttemptsHelp": "Obvestila so onemogočena, ko se tolikokrat neuspelo pošljejo",
|
||||||
|
"LabelNotificationsMaxQueueSize": "Največja velikost čakalne vrste za dogodke obvestil",
|
||||||
|
"LabelNotificationsMaxQueueSizeHelp": "Dogodki so omejeni na sprožitev 1 na sekundo. Dogodki bodo prezrti, če je čakalna vrsta najvišja. To preprečuje neželeno pošiljanje obvestil.",
|
||||||
|
"LabelNumberOfBooks": "Število knjig",
|
||||||
|
"LabelNumberOfEpisodes": "# od epizod",
|
||||||
|
"LabelOpenIDAdvancedPermsClaimDescription": "Ime zahtevka OpenID, ki vsebuje napredna dovoljenja za uporabniška dejanja v aplikaciji, ki bodo veljala za neskrbniške vloge (<b>če je konfigurirano</b>). Če trditev manjka v odgovoru, bo dostop do ABS zavrnjen. Če ena možnost manjka, bo obravnavana kot <code>false</code>. Zagotovite, da se zahtevek ponudnika identitete ujema s pričakovano strukturo:",
|
||||||
|
"LabelOpenIDClaims": "Pustite naslednje možnosti prazne, da onemogočite napredno dodeljevanje skupin in dovoljenj, nato pa samodejno dodelite skupino 'Uporabnik'.",
|
||||||
|
"LabelOpenIDGroupClaimDescription": "Ime zahtevka OpenID, ki vsebuje seznam uporabnikovih skupin. Običajno imenovane <code>skupine</code>. <b>Če je konfigurirana</b>, bo aplikacija samodejno dodelila vloge na podlagi članstva v skupini uporabnika, pod pogojem, da so te skupine v zahtevku poimenovane 'admin', 'user' ali 'guest' brez razlikovanja med velikimi in malimi črkami. Zahtevek mora vsebovati seznam in če uporabnik pripada več skupinam, mu aplikacija dodeli vlogo, ki ustreza najvišjemu nivoju dostopa. Če se nobena skupina ne ujema, bo dostop zavrnjen.",
|
||||||
|
"LabelOpenRSSFeed": "Odpri vir RSS",
|
||||||
|
"LabelOverwrite": "Prepiši",
|
||||||
|
"LabelPassword": "Geslo",
|
||||||
|
"LabelPath": "Pot",
|
||||||
|
"LabelPermanent": "Trajno",
|
||||||
|
"LabelPermissionsAccessAllLibraries": "Lahko dostopa do vseh knjižnic",
|
||||||
|
"LabelPermissionsAccessAllTags": "Lahko dostopa do vseh oznak",
|
||||||
|
"LabelPermissionsAccessExplicitContent": "Lahko dostopa do eksplicitne vsebine",
|
||||||
|
"LabelPermissionsDelete": "Lahko briše",
|
||||||
|
"LabelPermissionsDownload": "Lahko prenaša",
|
||||||
|
"LabelPermissionsUpdate": "Lahko posodablja",
|
||||||
|
"LabelPermissionsUpload": "Lahko nalaga",
|
||||||
|
"MessageConfirmPurgeCache": "Čiščenje predpomnilnika bo izbrisalo celoten imenik v <code>/metadata/cache</code>. <br /><br />Ali ste prepričani, da želite odstraniti imenik predpomnilnika?",
|
||||||
|
"MessageConfirmPurgeItemsCache": "Čiščenje predpomnilnika elementov bo izbrisalo celoten imenik na <code>/metadata/cache/items</code>.<br />Ste prepričani?"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.13.1",
|
"version": "2.13.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.13.1",
|
"version": "2.13.2",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.13.1",
|
"version": "2.13.2",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast server",
|
"description": "Self-hosted audiobook and podcast server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
+1
-21
@@ -442,26 +442,6 @@ class Database {
|
|||||||
await this.models.feed.removeById(feedId)
|
await this.models.feed.removeById(feedId)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSeries(oldSeries) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
return this.models.series.updateFromOld(oldSeries)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createSeries(oldSeries) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
await this.models.series.createFromOld(oldSeries)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createBulkSeries(oldSeriesObjs) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
await this.models.series.createBulkFromOld(oldSeriesObjs)
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeSeries(seriesId) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
await this.models.series.removeById(seriesId)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createBulkBookAuthors(bookAuthors) {
|
async createBulkBookAuthors(bookAuthors) {
|
||||||
if (!this.sequelize) return false
|
if (!this.sequelize) return false
|
||||||
await this.models.bookAuthor.bulkCreate(bookAuthors)
|
await this.models.bookAuthor.bulkCreate(bookAuthors)
|
||||||
@@ -678,7 +658,7 @@ class Database {
|
|||||||
*/
|
*/
|
||||||
async getSeriesIdByName(libraryId, seriesName) {
|
async getSeriesIdByName(libraryId, seriesName) {
|
||||||
if (!this.libraryFilterData[libraryId]) {
|
if (!this.libraryFilterData[libraryId]) {
|
||||||
return (await this.seriesModel.getOldByNameAndLibrary(seriesName, libraryId))?.id || null
|
return (await this.seriesModel.getByNameAndLibrary(seriesName, libraryId))?.id || null
|
||||||
}
|
}
|
||||||
return this.libraryFilterData[libraryId].series.find((se) => se.name === seriesName)?.id || null
|
return this.libraryFilterData[libraryId].series.find((se) => se.name === seriesName)?.id || null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ class AuthorController {
|
|||||||
let hasUpdated = false
|
let hasUpdated = false
|
||||||
|
|
||||||
const authorNameUpdate = payload.name !== undefined && payload.name !== req.author.name
|
const authorNameUpdate = payload.name !== undefined && payload.name !== req.author.name
|
||||||
|
if (authorNameUpdate) {
|
||||||
|
payload.lastFirst = Database.authorModel.getLastFirst(payload.name)
|
||||||
|
}
|
||||||
|
|
||||||
// Check if author name matches another author and merge the authors
|
// Check if author name matches another author and merge the authors
|
||||||
let existingAuthor = null
|
let existingAuthor = null
|
||||||
@@ -169,6 +172,11 @@ class AuthorController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If lastFirst is not set, get it from the name
|
||||||
|
if (!authorNameUpdate && !req.author.lastFirst) {
|
||||||
|
payload.lastFirst = Database.authorModel.getLastFirst(req.author.name)
|
||||||
|
}
|
||||||
|
|
||||||
// Regular author update
|
// Regular author update
|
||||||
req.author.set(payload)
|
req.author.set(payload)
|
||||||
if (req.author.changed()) {
|
if (req.author.changed()) {
|
||||||
|
|||||||
@@ -629,11 +629,10 @@ class LibraryController {
|
|||||||
|
|
||||||
const series = await Database.seriesModel.findByPk(req.params.seriesId)
|
const series = await Database.seriesModel.findByPk(req.params.seriesId)
|
||||||
if (!series) return res.sendStatus(404)
|
if (!series) return res.sendStatus(404)
|
||||||
const oldSeries = series.getOldSeries()
|
|
||||||
|
|
||||||
const libraryItemsInSeries = await libraryItemsBookFilters.getLibraryItemsForSeries(oldSeries, req.user)
|
const libraryItemsInSeries = await libraryItemsBookFilters.getLibraryItemsForSeries(series, req.user)
|
||||||
|
|
||||||
const seriesJson = oldSeries.toJSON()
|
const seriesJson = series.toOldJSON()
|
||||||
if (include.includes('progress')) {
|
if (include.includes('progress')) {
|
||||||
const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.user.getMediaProgress(li.media.id)?.isFinished)
|
const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.user.getMediaProgress(li.media.id)?.isFinished)
|
||||||
seriesJson.progress = {
|
seriesJson.progress = {
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class RSSFeedController {
|
|||||||
async openRSSFeedForSeries(req, res) {
|
async openRSSFeedForSeries(req, res) {
|
||||||
const options = req.body || {}
|
const options = req.body || {}
|
||||||
|
|
||||||
const series = await Database.seriesModel.getOldById(req.params.seriesId)
|
const series = await Database.seriesModel.findByPk(req.params.seriesId)
|
||||||
if (!series) return res.sendStatus(404)
|
if (!series) return res.sendStatus(404)
|
||||||
|
|
||||||
// Check request body options exist
|
// Check request body options exist
|
||||||
@@ -140,7 +140,7 @@ class RSSFeedController {
|
|||||||
return res.status(400).send('Slug already in use')
|
return res.status(400).send('Slug already in use')
|
||||||
}
|
}
|
||||||
|
|
||||||
const seriesJson = series.toJSON()
|
const seriesJson = series.toOldJSON()
|
||||||
|
|
||||||
// Get books in series that have audio tracks
|
// Get books in series that have audio tracks
|
||||||
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilter
|
|||||||
* @property {import('../models/User')} user
|
* @property {import('../models/User')} user
|
||||||
*
|
*
|
||||||
* @typedef {Request & RequestUserObject} RequestWithUser
|
* @typedef {Request & RequestUserObject} RequestWithUser
|
||||||
|
*
|
||||||
|
* @typedef RequestEntityObject
|
||||||
|
* @property {import('../models/Series')} series
|
||||||
|
*
|
||||||
|
* @typedef {RequestWithUser & RequestEntityObject} SeriesControllerRequest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class SeriesController {
|
class SeriesController {
|
||||||
@@ -21,7 +26,7 @@ class SeriesController {
|
|||||||
* TODO: Update mobile app to use /api/libraries/:id/series/:seriesId API route instead
|
* TODO: Update mobile app to use /api/libraries/:id/series/:seriesId API route instead
|
||||||
* Series are not library specific so we need to know what the library id is
|
* Series are not library specific so we need to know what the library id is
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {SeriesControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async findOne(req, res) {
|
async findOne(req, res) {
|
||||||
@@ -30,7 +35,7 @@ class SeriesController {
|
|||||||
.map((v) => v.trim())
|
.map((v) => v.trim())
|
||||||
.filter((v) => !!v)
|
.filter((v) => !!v)
|
||||||
|
|
||||||
const seriesJson = req.series.toJSON()
|
const seriesJson = req.series.toOldJSON()
|
||||||
|
|
||||||
// Add progress map with isFinished flag
|
// Add progress map with isFinished flag
|
||||||
if (include.includes('progress')) {
|
if (include.includes('progress')) {
|
||||||
@@ -54,17 +59,28 @@ class SeriesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* TODO: Currently unused in the client, should check for duplicate name
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {SeriesControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async update(req, res) {
|
async update(req, res) {
|
||||||
const hasUpdated = req.series.update(req.body)
|
const keysToUpdate = ['name', 'description']
|
||||||
if (hasUpdated) {
|
const payload = {}
|
||||||
await Database.updateSeries(req.series)
|
for (const key of keysToUpdate) {
|
||||||
SocketAuthority.emitter('series_updated', req.series.toJSON())
|
if (req.body[key] !== undefined && typeof req.body[key] === 'string') {
|
||||||
|
payload[key] = req.body[key]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res.json(req.series.toJSON())
|
if (!Object.keys(payload).length) {
|
||||||
|
return res.status(400).send('No valid fields to update')
|
||||||
|
}
|
||||||
|
req.series.set(payload)
|
||||||
|
if (req.series.changed()) {
|
||||||
|
await req.series.save()
|
||||||
|
SocketAuthority.emitter('series_updated', req.series.toOldJSON())
|
||||||
|
}
|
||||||
|
res.json(req.series.toOldJSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,7 +90,7 @@ class SeriesController {
|
|||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
async middleware(req, res, next) {
|
async middleware(req, res, next) {
|
||||||
const series = await Database.seriesModel.getOldById(req.params.id)
|
const series = await Database.seriesModel.findByPk(req.params.id)
|
||||||
if (!series) return res.sendStatus(404)
|
if (!series) return res.sendStatus(404)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class RssFeedManager {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else if (feedObj.entityType === 'series') {
|
} else if (feedObj.entityType === 'series') {
|
||||||
const series = await Database.seriesModel.getOldById(feedObj.entityId)
|
const series = await Database.seriesModel.findByPk(feedObj.entityId)
|
||||||
if (!series) {
|
if (!series) {
|
||||||
Logger.error(`[RssFeedManager] Removing feed "${feedObj.id}". Series "${feedObj.entityId}" not found`)
|
Logger.error(`[RssFeedManager] Removing feed "${feedObj.id}". Series "${feedObj.entityId}" not found`)
|
||||||
return false
|
return false
|
||||||
@@ -133,9 +133,9 @@ class RssFeedManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (feed.entityType === 'series') {
|
} else if (feed.entityType === 'series') {
|
||||||
const series = await Database.seriesModel.getOldById(feed.entityId)
|
const series = await Database.seriesModel.findByPk(feed.entityId)
|
||||||
if (series) {
|
if (series) {
|
||||||
const seriesJson = series.toJSON()
|
const seriesJson = series.toOldJSON()
|
||||||
|
|
||||||
// Get books in series that have audio tracks
|
// Get books in series that have audio tracks
|
||||||
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
||||||
|
const parseNameString = require('../utils/parsers/parseNameString')
|
||||||
|
|
||||||
class Author extends Model {
|
class Author extends Model {
|
||||||
constructor(values, options) {
|
constructor(values, options) {
|
||||||
@@ -24,6 +25,16 @@ class Author extends Model {
|
|||||||
this.createdAt
|
this.createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static getLastFirst(name) {
|
||||||
|
if (!name) return null
|
||||||
|
return parseNameString.nameToLastFirst(name)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if author exists
|
* Check if author exists
|
||||||
* @param {string} authorId
|
* @param {string} authorId
|
||||||
|
|||||||
+32
-79
@@ -1,6 +1,6 @@
|
|||||||
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
||||||
|
|
||||||
const oldSeries = require('../objects/entities/Series')
|
const { getTitlePrefixAtEnd } = require('../utils/index')
|
||||||
|
|
||||||
class Series extends Model {
|
class Series extends Model {
|
||||||
constructor(values, options) {
|
constructor(values, options) {
|
||||||
@@ -22,70 +22,6 @@ class Series extends Model {
|
|||||||
this.updatedAt
|
this.updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getAllOldSeries() {
|
|
||||||
const series = await this.findAll()
|
|
||||||
return series.map((se) => se.getOldSeries())
|
|
||||||
}
|
|
||||||
|
|
||||||
getOldSeries() {
|
|
||||||
return new oldSeries({
|
|
||||||
id: this.id,
|
|
||||||
name: this.name,
|
|
||||||
description: this.description,
|
|
||||||
libraryId: this.libraryId,
|
|
||||||
addedAt: this.createdAt.valueOf(),
|
|
||||||
updatedAt: this.updatedAt.valueOf()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static updateFromOld(oldSeries) {
|
|
||||||
const series = this.getFromOld(oldSeries)
|
|
||||||
return this.update(series, {
|
|
||||||
where: {
|
|
||||||
id: series.id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static createFromOld(oldSeries) {
|
|
||||||
const series = this.getFromOld(oldSeries)
|
|
||||||
return this.create(series)
|
|
||||||
}
|
|
||||||
|
|
||||||
static createBulkFromOld(oldSeriesObjs) {
|
|
||||||
const series = oldSeriesObjs.map(this.getFromOld)
|
|
||||||
return this.bulkCreate(series)
|
|
||||||
}
|
|
||||||
|
|
||||||
static getFromOld(oldSeries) {
|
|
||||||
return {
|
|
||||||
id: oldSeries.id,
|
|
||||||
name: oldSeries.name,
|
|
||||||
nameIgnorePrefix: oldSeries.nameIgnorePrefix,
|
|
||||||
description: oldSeries.description,
|
|
||||||
libraryId: oldSeries.libraryId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeById(seriesId) {
|
|
||||||
return this.destroy({
|
|
||||||
where: {
|
|
||||||
id: seriesId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get oldSeries by id
|
|
||||||
* @param {string} seriesId
|
|
||||||
* @returns {Promise<oldSeries>}
|
|
||||||
*/
|
|
||||||
static async getOldById(seriesId) {
|
|
||||||
const series = await this.findByPk(seriesId)
|
|
||||||
if (!series) return null
|
|
||||||
return series.getOldSeries()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if series exists
|
* Check if series exists
|
||||||
* @param {string} seriesId
|
* @param {string} seriesId
|
||||||
@@ -96,24 +32,21 @@ class Series extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get old series by name and libraryId. name case insensitive
|
* Get series by name and libraryId. name case insensitive
|
||||||
*
|
*
|
||||||
* @param {string} seriesName
|
* @param {string} seriesName
|
||||||
* @param {string} libraryId
|
* @param {string} libraryId
|
||||||
* @returns {Promise<oldSeries>}
|
* @returns {Promise<Series>}
|
||||||
*/
|
*/
|
||||||
static async getOldByNameAndLibrary(seriesName, libraryId) {
|
static async getByNameAndLibrary(seriesName, libraryId) {
|
||||||
const series = (
|
return this.findOne({
|
||||||
await this.findOne({
|
where: [
|
||||||
where: [
|
where(fn('lower', col('name')), seriesName.toLowerCase()),
|
||||||
where(fn('lower', col('name')), seriesName.toLowerCase()),
|
{
|
||||||
{
|
libraryId
|
||||||
libraryId
|
}
|
||||||
}
|
]
|
||||||
]
|
})
|
||||||
})
|
|
||||||
)?.getOldSeries()
|
|
||||||
return series
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -163,6 +96,26 @@ class Series extends Model {
|
|||||||
})
|
})
|
||||||
Series.belongsTo(library)
|
Series.belongsTo(library)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toOldJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
name: this.name,
|
||||||
|
nameIgnorePrefix: getTitlePrefixAtEnd(this.name),
|
||||||
|
description: this.description,
|
||||||
|
addedAt: this.createdAt.valueOf(),
|
||||||
|
updatedAt: this.updatedAt.valueOf(),
|
||||||
|
libraryId: this.libraryId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSONMinimal(sequence) {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
name: this.name,
|
||||||
|
sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Series
|
module.exports = Series
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
const uuidv4 = require("uuid").v4
|
|
||||||
const { getTitleIgnorePrefix, getTitlePrefixAtEnd } = require('../../utils/index')
|
|
||||||
|
|
||||||
class Series {
|
|
||||||
constructor(series) {
|
|
||||||
this.id = null
|
|
||||||
this.name = null
|
|
||||||
this.description = null
|
|
||||||
this.addedAt = null
|
|
||||||
this.updatedAt = null
|
|
||||||
this.libraryId = null
|
|
||||||
|
|
||||||
if (series) {
|
|
||||||
this.construct(series)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(series) {
|
|
||||||
this.id = series.id
|
|
||||||
this.name = series.name
|
|
||||||
this.description = series.description || null
|
|
||||||
this.addedAt = series.addedAt
|
|
||||||
this.updatedAt = series.updatedAt
|
|
||||||
this.libraryId = series.libraryId
|
|
||||||
}
|
|
||||||
|
|
||||||
get nameIgnorePrefix() {
|
|
||||||
if (!this.name) return ''
|
|
||||||
return getTitleIgnorePrefix(this.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
name: this.name,
|
|
||||||
nameIgnorePrefix: getTitlePrefixAtEnd(this.name),
|
|
||||||
description: this.description,
|
|
||||||
addedAt: this.addedAt,
|
|
||||||
updatedAt: this.updatedAt,
|
|
||||||
libraryId: this.libraryId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONMinimal(sequence) {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
name: this.name,
|
|
||||||
sequence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(data, libraryId) {
|
|
||||||
this.id = uuidv4()
|
|
||||||
this.name = data.name
|
|
||||||
this.description = data.description || null
|
|
||||||
this.addedAt = Date.now()
|
|
||||||
this.updatedAt = Date.now()
|
|
||||||
this.libraryId = libraryId
|
|
||||||
}
|
|
||||||
|
|
||||||
update(series) {
|
|
||||||
if (!series) return false
|
|
||||||
const keysToUpdate = ['name', 'description']
|
|
||||||
let hasUpdated = false
|
|
||||||
for (const key of keysToUpdate) {
|
|
||||||
if (series[key] !== undefined && series[key] !== this[key]) {
|
|
||||||
this[key] = series[key]
|
|
||||||
hasUpdated = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasUpdated
|
|
||||||
}
|
|
||||||
|
|
||||||
checkNameEquals(name) {
|
|
||||||
if (!name || !this.name) return false
|
|
||||||
return this.name.toLowerCase() == name.toLowerCase().trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = Series
|
|
||||||
@@ -33,7 +33,7 @@ const CustomMetadataProviderController = require('../controllers/CustomMetadataP
|
|||||||
const MiscController = require('../controllers/MiscController')
|
const MiscController = require('../controllers/MiscController')
|
||||||
const ShareController = require('../controllers/ShareController')
|
const ShareController = require('../controllers/ShareController')
|
||||||
|
|
||||||
const Series = require('../objects/entities/Series')
|
const { getTitleIgnorePrefix } = require('../utils/index')
|
||||||
|
|
||||||
class ApiRouter {
|
class ApiRouter {
|
||||||
constructor(Server) {
|
constructor(Server) {
|
||||||
@@ -524,13 +524,15 @@ class ApiRouter {
|
|||||||
async removeEmptySeries(series) {
|
async removeEmptySeries(series) {
|
||||||
await this.rssFeedManager.closeFeedForEntityId(series.id)
|
await this.rssFeedManager.closeFeedForEntityId(series.id)
|
||||||
Logger.info(`[ApiRouter] Series "${series.name}" is now empty. Removing series`)
|
Logger.info(`[ApiRouter] Series "${series.name}" is now empty. Removing series`)
|
||||||
await Database.removeSeries(series.id)
|
|
||||||
// Remove series from library filter data
|
// Remove series from library filter data
|
||||||
Database.removeSeriesFromFilterData(series.libraryId, series.id)
|
Database.removeSeriesFromFilterData(series.libraryId, series.id)
|
||||||
SocketAuthority.emitter('series_removed', {
|
SocketAuthority.emitter('series_removed', {
|
||||||
id: series.id,
|
id: series.id,
|
||||||
libraryId: series.libraryId
|
libraryId: series.libraryId
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await series.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserListeningSessionsHelper(userId) {
|
async getUserListeningSessionsHelper(userId) {
|
||||||
@@ -619,6 +621,7 @@ class ApiRouter {
|
|||||||
if (!author) {
|
if (!author) {
|
||||||
author = await Database.authorModel.create({
|
author = await Database.authorModel.create({
|
||||||
name: authorName,
|
name: authorName,
|
||||||
|
lastFirst: Database.authorModel.getLastFirst(authorName),
|
||||||
libraryId
|
libraryId
|
||||||
})
|
})
|
||||||
Logger.debug(`[ApiRouter] Creating new author "${author.name}"`)
|
Logger.debug(`[ApiRouter] Creating new author "${author.name}"`)
|
||||||
@@ -663,11 +666,14 @@ class ApiRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!mediaMetadata.series[i].id) {
|
if (!mediaMetadata.series[i].id) {
|
||||||
let seriesItem = await Database.seriesModel.getOldByNameAndLibrary(seriesName, libraryId)
|
let seriesItem = await Database.seriesModel.getByNameAndLibrary(seriesName, libraryId)
|
||||||
if (!seriesItem) {
|
if (!seriesItem) {
|
||||||
seriesItem = new Series()
|
seriesItem = await Database.seriesModel.create({
|
||||||
seriesItem.setData(mediaMetadata.series[i], libraryId)
|
name: seriesName,
|
||||||
Logger.debug(`[ApiRouter] Created new series "${seriesItem.name}"`)
|
nameIgnorePrefix: getTitleIgnorePrefix(seriesName),
|
||||||
|
libraryId
|
||||||
|
})
|
||||||
|
Logger.debug(`[ApiRouter] Creating new series "${seriesItem.name}"`)
|
||||||
newSeries.push(seriesItem)
|
newSeries.push(seriesItem)
|
||||||
// Update filter data
|
// Update filter data
|
||||||
Database.addSeriesToFilterData(libraryId, seriesItem.name, seriesItem.id)
|
Database.addSeriesToFilterData(libraryId, seriesItem.name, seriesItem.id)
|
||||||
@@ -680,10 +686,9 @@ class ApiRouter {
|
|||||||
// Remove series without an id
|
// Remove series without an id
|
||||||
mediaMetadata.series = mediaMetadata.series.filter((se) => se.id)
|
mediaMetadata.series = mediaMetadata.series.filter((se) => se.id)
|
||||||
if (newSeries.length) {
|
if (newSeries.length) {
|
||||||
await Database.createBulkSeries(newSeries)
|
|
||||||
SocketAuthority.emitter(
|
SocketAuthority.emitter(
|
||||||
'multiple_series_added',
|
'multiple_series_added',
|
||||||
newSeries.map((se) => se.toJSON())
|
newSeries.map((se) => se.toOldJSON())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+133
-125
@@ -1,4 +1,4 @@
|
|||||||
const uuidv4 = require("uuid").v4
|
const uuidv4 = require('uuid').v4
|
||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const sequelize = require('sequelize')
|
const sequelize = require('sequelize')
|
||||||
const { LogLevel } = require('../utils/constants')
|
const { LogLevel } = require('../utils/constants')
|
||||||
@@ -13,14 +13,14 @@ const AudioFile = require('../objects/files/AudioFile')
|
|||||||
const CoverManager = require('../managers/CoverManager')
|
const CoverManager = require('../managers/CoverManager')
|
||||||
const LibraryFile = require('../objects/files/LibraryFile')
|
const LibraryFile = require('../objects/files/LibraryFile')
|
||||||
const SocketAuthority = require('../SocketAuthority')
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
const fsExtra = require("../libs/fsExtra")
|
const fsExtra = require('../libs/fsExtra')
|
||||||
const BookFinder = require('../finders/BookFinder')
|
const BookFinder = require('../finders/BookFinder')
|
||||||
|
|
||||||
const LibraryScan = require("./LibraryScan")
|
const LibraryScan = require('./LibraryScan')
|
||||||
const OpfFileScanner = require('./OpfFileScanner')
|
const OpfFileScanner = require('./OpfFileScanner')
|
||||||
const NfoFileScanner = require('./NfoFileScanner')
|
const NfoFileScanner = require('./NfoFileScanner')
|
||||||
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
|
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
|
||||||
const EBookFile = require("../objects/files/EBookFile")
|
const EBookFile = require('../objects/files/EBookFile')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata for books pulled from files
|
* Metadata for books pulled from files
|
||||||
@@ -46,13 +46,13 @@ const EBookFile = require("../objects/files/EBookFile")
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class BookScanner {
|
class BookScanner {
|
||||||
constructor() { }
|
constructor() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('../models/LibraryItem')} existingLibraryItem
|
* @param {import('../models/LibraryItem')} existingLibraryItem
|
||||||
* @param {import('./LibraryItemScanData')} libraryItemData
|
* @param {import('./LibraryItemScanData')} libraryItemData
|
||||||
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {LibraryScan} libraryScan
|
||||||
* @returns {Promise<{libraryItem:import('../models/LibraryItem'), wasUpdated:boolean}>}
|
* @returns {Promise<{libraryItem:import('../models/LibraryItem'), wasUpdated:boolean}>}
|
||||||
*/
|
*/
|
||||||
async rescanExistingBookLibraryItem(existingLibraryItem, libraryItemData, librarySettings, libraryScan) {
|
async rescanExistingBookLibraryItem(existingLibraryItem, libraryItemData, librarySettings, libraryScan) {
|
||||||
@@ -81,19 +81,23 @@ class BookScanner {
|
|||||||
let hasMediaChanges = libraryItemData.hasAudioFileChanges || libraryItemData.audioLibraryFiles.length !== media.audioFiles.length
|
let hasMediaChanges = libraryItemData.hasAudioFileChanges || libraryItemData.audioLibraryFiles.length !== media.audioFiles.length
|
||||||
if (hasMediaChanges) {
|
if (hasMediaChanges) {
|
||||||
// Filter out audio files that were removed
|
// Filter out audio files that were removed
|
||||||
media.audioFiles = media.audioFiles.filter(af => !libraryItemData.checkAudioFileRemoved(af))
|
media.audioFiles = media.audioFiles.filter((af) => !libraryItemData.checkAudioFileRemoved(af))
|
||||||
|
|
||||||
// Update audio files that were modified
|
// Update audio files that were modified
|
||||||
if (libraryItemData.audioLibraryFilesModified.length) {
|
if (libraryItemData.audioLibraryFilesModified.length) {
|
||||||
let scannedAudioFiles = await AudioFileScanner.executeMediaFileScans(existingLibraryItem.mediaType, libraryItemData, libraryItemData.audioLibraryFilesModified.map(lf => lf.new))
|
let scannedAudioFiles = await AudioFileScanner.executeMediaFileScans(
|
||||||
|
existingLibraryItem.mediaType,
|
||||||
|
libraryItemData,
|
||||||
|
libraryItemData.audioLibraryFilesModified.map((lf) => lf.new)
|
||||||
|
)
|
||||||
media.audioFiles = media.audioFiles.map((audioFileObj) => {
|
media.audioFiles = media.audioFiles.map((audioFileObj) => {
|
||||||
let matchedScannedAudioFile = scannedAudioFiles.find(saf => saf.metadata.path === audioFileObj.metadata.path)
|
let matchedScannedAudioFile = scannedAudioFiles.find((saf) => saf.metadata.path === audioFileObj.metadata.path)
|
||||||
if (!matchedScannedAudioFile) {
|
if (!matchedScannedAudioFile) {
|
||||||
matchedScannedAudioFile = scannedAudioFiles.find(saf => saf.ino === audioFileObj.ino)
|
matchedScannedAudioFile = scannedAudioFiles.find((saf) => saf.ino === audioFileObj.ino)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedScannedAudioFile) {
|
if (matchedScannedAudioFile) {
|
||||||
scannedAudioFiles = scannedAudioFiles.filter(saf => saf !== matchedScannedAudioFile)
|
scannedAudioFiles = scannedAudioFiles.filter((saf) => saf !== matchedScannedAudioFile)
|
||||||
const audioFile = new AudioFile(audioFileObj)
|
const audioFile = new AudioFile(audioFileObj)
|
||||||
audioFile.updateFromScan(matchedScannedAudioFile)
|
audioFile.updateFromScan(matchedScannedAudioFile)
|
||||||
return audioFile.toJSON()
|
return audioFile.toJSON()
|
||||||
@@ -115,7 +119,7 @@ class BookScanner {
|
|||||||
// Add audio library files that are not already set on the book (safety check)
|
// Add audio library files that are not already set on the book (safety check)
|
||||||
let audioLibraryFilesToAdd = []
|
let audioLibraryFilesToAdd = []
|
||||||
for (const audioLibraryFile of libraryItemData.audioLibraryFiles) {
|
for (const audioLibraryFile of libraryItemData.audioLibraryFiles) {
|
||||||
if (!media.audioFiles.some(af => af.ino === audioLibraryFile.ino)) {
|
if (!media.audioFiles.some((af) => af.ino === audioLibraryFile.ino)) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Existing audio library file "${audioLibraryFile.metadata.relPath}" was not set on book "${media.title}" so setting it now`)
|
libraryScan.addLog(LogLevel.DEBUG, `Existing audio library file "${audioLibraryFile.metadata.relPath}" was not set on book "${media.title}" so setting it now`)
|
||||||
|
|
||||||
audioLibraryFilesToAdd.push(audioLibraryFile)
|
audioLibraryFilesToAdd.push(audioLibraryFile)
|
||||||
@@ -139,14 +143,14 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if cover was removed
|
// Check if cover was removed
|
||||||
if (media.coverPath && libraryItemData.imageLibraryFilesRemoved.some(lf => lf.metadata.path === media.coverPath) && !(await fsExtra.pathExists(media.coverPath))) {
|
if (media.coverPath && libraryItemData.imageLibraryFilesRemoved.some((lf) => lf.metadata.path === media.coverPath) && !(await fsExtra.pathExists(media.coverPath))) {
|
||||||
media.coverPath = null
|
media.coverPath = null
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update cover if it was modified
|
// Update cover if it was modified
|
||||||
if (media.coverPath && libraryItemData.imageLibraryFilesModified.length) {
|
if (media.coverPath && libraryItemData.imageLibraryFilesModified.length) {
|
||||||
let coverMatch = libraryItemData.imageLibraryFilesModified.find(iFile => iFile.old.metadata.path === media.coverPath)
|
let coverMatch = libraryItemData.imageLibraryFilesModified.find((iFile) => iFile.old.metadata.path === media.coverPath)
|
||||||
if (coverMatch) {
|
if (coverMatch) {
|
||||||
const coverPath = coverMatch.new.metadata.path
|
const coverPath = coverMatch.new.metadata.path
|
||||||
if (coverPath !== media.coverPath) {
|
if (coverPath !== media.coverPath) {
|
||||||
@@ -161,7 +165,7 @@ class BookScanner {
|
|||||||
// Check if cover is not set and image files were found
|
// Check if cover is not set and image files were found
|
||||||
if (!media.coverPath && libraryItemData.imageLibraryFiles.length) {
|
if (!media.coverPath && libraryItemData.imageLibraryFiles.length) {
|
||||||
// Prefer using a cover image with the name "cover" otherwise use the first image
|
// Prefer using a cover image with the name "cover" otherwise use the first image
|
||||||
const coverMatch = libraryItemData.imageLibraryFiles.find(iFile => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
const coverMatch = libraryItemData.imageLibraryFiles.find((iFile) => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
||||||
media.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
media.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
@@ -174,7 +178,7 @@ class BookScanner {
|
|||||||
|
|
||||||
// Update ebook if it was modified
|
// Update ebook if it was modified
|
||||||
if (media.ebookFile && libraryItemData.ebookLibraryFilesModified.length) {
|
if (media.ebookFile && libraryItemData.ebookLibraryFilesModified.length) {
|
||||||
let ebookMatch = libraryItemData.ebookLibraryFilesModified.find(eFile => eFile.old.metadata.path === media.ebookFile.metadata.path)
|
let ebookMatch = libraryItemData.ebookLibraryFilesModified.find((eFile) => eFile.old.metadata.path === media.ebookFile.metadata.path)
|
||||||
if (ebookMatch) {
|
if (ebookMatch) {
|
||||||
const ebookFile = new EBookFile(ebookMatch.new)
|
const ebookFile = new EBookFile(ebookMatch.new)
|
||||||
ebookFile.ebookFormat = ebookFile.metadata.ext.slice(1).toLowerCase()
|
ebookFile.ebookFormat = ebookFile.metadata.ext.slice(1).toLowerCase()
|
||||||
@@ -188,7 +192,7 @@ class BookScanner {
|
|||||||
// Check if ebook is not set and ebooks were found
|
// Check if ebook is not set and ebooks were found
|
||||||
if (!media.ebookFile && !librarySettings.audiobooksOnly && libraryItemData.ebookLibraryFiles.length) {
|
if (!media.ebookFile && !librarySettings.audiobooksOnly && libraryItemData.ebookLibraryFiles.length) {
|
||||||
// Prefer to use an epub ebook then fallback to the first ebook found
|
// Prefer to use an epub ebook then fallback to the first ebook found
|
||||||
let ebookLibraryFile = libraryItemData.ebookLibraryFiles.find(lf => lf.metadata.ext.slice(1).toLowerCase() === 'epub')
|
let ebookLibraryFile = libraryItemData.ebookLibraryFiles.find((lf) => lf.metadata.ext.slice(1).toLowerCase() === 'epub')
|
||||||
if (!ebookLibraryFile) ebookLibraryFile = libraryItemData.ebookLibraryFiles[0]
|
if (!ebookLibraryFile) ebookLibraryFile = libraryItemData.ebookLibraryFiles[0]
|
||||||
ebookLibraryFile = ebookLibraryFile.toJSON()
|
ebookLibraryFile = ebookLibraryFile.toJSON()
|
||||||
// Ebook file is the same as library file except for additional `ebookFormat`
|
// Ebook file is the same as library file except for additional `ebookFormat`
|
||||||
@@ -213,7 +217,7 @@ class BookScanner {
|
|||||||
if (key === 'authors') {
|
if (key === 'authors') {
|
||||||
// Check for authors added
|
// Check for authors added
|
||||||
for (const authorName of bookMetadata.authors) {
|
for (const authorName of bookMetadata.authors) {
|
||||||
if (!media.authors.some(au => au.name === authorName)) {
|
if (!media.authors.some((au) => au.name === authorName)) {
|
||||||
const existingAuthorId = await Database.getAuthorIdByName(libraryItemData.libraryId, authorName)
|
const existingAuthorId = await Database.getAuthorIdByName(libraryItemData.libraryId, authorName)
|
||||||
if (existingAuthorId) {
|
if (existingAuthorId) {
|
||||||
await Database.bookAuthorModel.create({
|
await Database.bookAuthorModel.create({
|
||||||
@@ -225,7 +229,7 @@ class BookScanner {
|
|||||||
} else {
|
} else {
|
||||||
const newAuthor = await Database.authorModel.create({
|
const newAuthor = await Database.authorModel.create({
|
||||||
name: authorName,
|
name: authorName,
|
||||||
lastFirst: parseNameString.nameToLastFirst(authorName),
|
lastFirst: Database.authorModel.getLastFirst(authorName),
|
||||||
libraryId: libraryItemData.libraryId
|
libraryId: libraryItemData.libraryId
|
||||||
})
|
})
|
||||||
await media.addAuthor(newAuthor)
|
await media.addAuthor(newAuthor)
|
||||||
@@ -247,7 +251,7 @@ class BookScanner {
|
|||||||
} else if (key === 'series') {
|
} else if (key === 'series') {
|
||||||
// Check for series added
|
// Check for series added
|
||||||
for (const seriesObj of bookMetadata.series) {
|
for (const seriesObj of bookMetadata.series) {
|
||||||
const existingBookSeries = media.series.find(se => se.name === seriesObj.name)
|
const existingBookSeries = media.series.find((se) => se.name === seriesObj.name)
|
||||||
if (!existingBookSeries) {
|
if (!existingBookSeries) {
|
||||||
const existingSeriesId = await Database.getSeriesIdByName(libraryItemData.libraryId, seriesObj.name)
|
const existingSeriesId = await Database.getSeriesIdByName(libraryItemData.libraryId, seriesObj.name)
|
||||||
if (existingSeriesId) {
|
if (existingSeriesId) {
|
||||||
@@ -278,7 +282,7 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
// Check for series removed
|
// Check for series removed
|
||||||
for (const series of media.series) {
|
for (const series of media.series) {
|
||||||
if (!bookMetadata.series.some(se => se.name === series.name)) {
|
if (!bookMetadata.series.some((se) => se.name === series.name)) {
|
||||||
await series.bookSeries.destroy()
|
await series.bookSeries.destroy()
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" removed series "${series.name}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" removed series "${series.name}"`)
|
||||||
seriesUpdated = true
|
seriesUpdated = true
|
||||||
@@ -287,21 +291,21 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
} else if (key === 'genres') {
|
} else if (key === 'genres') {
|
||||||
const existingGenres = media.genres || []
|
const existingGenres = media.genres || []
|
||||||
if (bookMetadata.genres.some(g => !existingGenres.includes(g)) || existingGenres.some(g => !bookMetadata.genres.includes(g))) {
|
if (bookMetadata.genres.some((g) => !existingGenres.includes(g)) || existingGenres.some((g) => !bookMetadata.genres.includes(g))) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book genres "${existingGenres.join(',')}" => "${bookMetadata.genres.join(',')}" for book "${bookMetadata.title}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating book genres "${existingGenres.join(',')}" => "${bookMetadata.genres.join(',')}" for book "${bookMetadata.title}"`)
|
||||||
media.genres = bookMetadata.genres
|
media.genres = bookMetadata.genres
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
} else if (key === 'tags') {
|
} else if (key === 'tags') {
|
||||||
const existingTags = media.tags || []
|
const existingTags = media.tags || []
|
||||||
if (bookMetadata.tags.some(t => !existingTags.includes(t)) || existingTags.some(t => !bookMetadata.tags.includes(t))) {
|
if (bookMetadata.tags.some((t) => !existingTags.includes(t)) || existingTags.some((t) => !bookMetadata.tags.includes(t))) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book tags "${existingTags.join(',')}" => "${bookMetadata.tags.join(',')}" for book "${bookMetadata.title}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating book tags "${existingTags.join(',')}" => "${bookMetadata.tags.join(',')}" for book "${bookMetadata.title}"`)
|
||||||
media.tags = bookMetadata.tags
|
media.tags = bookMetadata.tags
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
} else if (key === 'narrators') {
|
} else if (key === 'narrators') {
|
||||||
const existingNarrators = media.narrators || []
|
const existingNarrators = media.narrators || []
|
||||||
if (bookMetadata.narrators.some(t => !existingNarrators.includes(t)) || existingNarrators.some(t => !bookMetadata.narrators.includes(t))) {
|
if (bookMetadata.narrators.some((t) => !existingNarrators.includes(t)) || existingNarrators.some((t) => !bookMetadata.narrators.includes(t))) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book narrators "${existingNarrators.join(',')}" => "${bookMetadata.narrators.join(',')}" for book "${bookMetadata.title}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating book narrators "${existingNarrators.join(',')}" => "${bookMetadata.narrators.join(',')}" for book "${bookMetadata.title}"`)
|
||||||
media.narrators = bookMetadata.narrators
|
media.narrators = bookMetadata.narrators
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
@@ -333,17 +337,13 @@ class BookScanner {
|
|||||||
if (authorsUpdated) {
|
if (authorsUpdated) {
|
||||||
media.authors = await media.getAuthors({
|
media.authors = await media.getAuthors({
|
||||||
joinTableAttributes: ['createdAt'],
|
joinTableAttributes: ['createdAt'],
|
||||||
order: [
|
order: [sequelize.literal(`bookAuthor.createdAt ASC`)]
|
||||||
sequelize.literal(`bookAuthor.createdAt ASC`)
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (seriesUpdated) {
|
if (seriesUpdated) {
|
||||||
media.series = await media.getSeries({
|
media.series = await media.getSeries({
|
||||||
joinTableAttributes: ['sequence', 'createdAt'],
|
joinTableAttributes: ['sequence', 'createdAt'],
|
||||||
order: [
|
order: [sequelize.literal(`bookSeries.createdAt ASC`)]
|
||||||
sequelize.literal(`bookSeries.createdAt ASC`)
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +367,10 @@ class BookScanner {
|
|||||||
|
|
||||||
// If no cover then search for cover if enabled in server settings
|
// If no cover then search for cover if enabled in server settings
|
||||||
if (!media.coverPath && Database.serverSettings.scannerFindCovers) {
|
if (!media.coverPath && Database.serverSettings.scannerFindCovers) {
|
||||||
const authorName = media.authors.map(au => au.name).filter(au => au).join(', ')
|
const authorName = media.authors
|
||||||
|
.map((au) => au.name)
|
||||||
|
.filter((au) => au)
|
||||||
|
.join(', ')
|
||||||
const coverPath = await this.searchForCover(existingLibraryItem.id, libraryItemDir, media.title, authorName, libraryScan)
|
const coverPath = await this.searchForCover(existingLibraryItem.id, libraryItemDir, media.title, authorName, libraryScan)
|
||||||
if (coverPath) {
|
if (coverPath) {
|
||||||
media.coverPath = coverPath
|
media.coverPath = coverPath
|
||||||
@@ -428,10 +431,10 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('./LibraryItemScanData')} libraryItemData
|
* @param {import('./LibraryItemScanData')} libraryItemData
|
||||||
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {LibraryScan} libraryScan
|
||||||
* @returns {Promise<import('../models/LibraryItem')>}
|
* @returns {Promise<import('../models/LibraryItem')>}
|
||||||
*/
|
*/
|
||||||
async scanNewBookLibraryItem(libraryItemData, librarySettings, libraryScan) {
|
async scanNewBookLibraryItem(libraryItemData, librarySettings, libraryScan) {
|
||||||
@@ -440,7 +443,7 @@ class BookScanner {
|
|||||||
scannedAudioFiles = AudioFileScanner.runSmartTrackOrder(libraryItemData.relPath, scannedAudioFiles)
|
scannedAudioFiles = AudioFileScanner.runSmartTrackOrder(libraryItemData.relPath, scannedAudioFiles)
|
||||||
|
|
||||||
// Find ebook file (prefer epub)
|
// Find ebook file (prefer epub)
|
||||||
let ebookLibraryFile = librarySettings.audiobooksOnly ? null : libraryItemData.ebookLibraryFiles.find(lf => lf.metadata.ext.slice(1).toLowerCase() === 'epub') || libraryItemData.ebookLibraryFiles[0]
|
let ebookLibraryFile = librarySettings.audiobooksOnly ? null : libraryItemData.ebookLibraryFiles.find((lf) => lf.metadata.ext.slice(1).toLowerCase() === 'epub') || libraryItemData.ebookLibraryFiles[0]
|
||||||
|
|
||||||
// Do not add library items that have no valid audio files and no ebook file
|
// Do not add library items that have no valid audio files and no ebook file
|
||||||
if (!ebookLibraryFile && !scannedAudioFiles.length) {
|
if (!ebookLibraryFile && !scannedAudioFiles.length) {
|
||||||
@@ -460,7 +463,7 @@ class BookScanner {
|
|||||||
bookMetadata.abridged = !!bookMetadata.abridged // Ensure boolean
|
bookMetadata.abridged = !!bookMetadata.abridged // Ensure boolean
|
||||||
|
|
||||||
let duration = 0
|
let duration = 0
|
||||||
scannedAudioFiles.forEach((af) => duration += (!isNaN(af.duration) ? Number(af.duration) : 0))
|
scannedAudioFiles.forEach((af) => (duration += !isNaN(af.duration) ? Number(af.duration) : 0))
|
||||||
const bookObject = {
|
const bookObject = {
|
||||||
...bookMetadata,
|
...bookMetadata,
|
||||||
audioFiles: scannedAudioFiles,
|
audioFiles: scannedAudioFiles,
|
||||||
@@ -482,7 +485,7 @@ class BookScanner {
|
|||||||
author: {
|
author: {
|
||||||
libraryId: libraryItemData.libraryId,
|
libraryId: libraryItemData.libraryId,
|
||||||
name: authorName,
|
name: authorName,
|
||||||
lastFirst: parseNameString.nameToLastFirst(authorName)
|
lastFirst: Database.authorModel.getLastFirst(authorName)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -619,11 +622,11 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('../models/Book').AudioFileObject[]} audioFiles
|
* @param {import('../models/Book').AudioFileObject[]} audioFiles
|
||||||
* @param {import('../utils/parsers/parseEbookMetadata').EBookFileScanData} ebookFileScanData
|
* @param {import('../utils/parsers/parseEbookMetadata').EBookFileScanData} ebookFileScanData
|
||||||
* @param {import('./LibraryItemScanData')} libraryItemData
|
* @param {import('./LibraryItemScanData')} libraryItemData
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {LibraryScan} libraryScan
|
||||||
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
||||||
* @param {string} [existingLibraryItemId]
|
* @param {string} [existingLibraryItemId]
|
||||||
* @returns {Promise<BookMetadataObject>}
|
* @returns {Promise<BookMetadataObject>}
|
||||||
@@ -664,7 +667,7 @@ class BookScanner {
|
|||||||
|
|
||||||
// Set cover from library file if one is found otherwise check audiofile
|
// Set cover from library file if one is found otherwise check audiofile
|
||||||
if (libraryItemData.imageLibraryFiles.length) {
|
if (libraryItemData.imageLibraryFiles.length) {
|
||||||
const coverMatch = libraryItemData.imageLibraryFiles.find(iFile => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
const coverMatch = libraryItemData.imageLibraryFiles.find((iFile) => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
||||||
bookMetadata.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
bookMetadata.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,16 +676,15 @@ class BookScanner {
|
|||||||
return bookMetadata
|
return bookMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static BookMetadataSourceHandler = class {
|
static BookMetadataSourceHandler = class {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Object} bookMetadata
|
* @param {Object} bookMetadata
|
||||||
* @param {import('../models/Book').AudioFileObject[]} audioFiles
|
* @param {import('../models/Book').AudioFileObject[]} audioFiles
|
||||||
* @param {import('../utils/parsers/parseEbookMetadata').EBookFileScanData} ebookFileScanData
|
* @param {import('../utils/parsers/parseEbookMetadata').EBookFileScanData} ebookFileScanData
|
||||||
* @param {import('./LibraryItemScanData')} libraryItemData
|
* @param {import('./LibraryItemScanData')} libraryItemData
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {LibraryScan} libraryScan
|
||||||
* @param {string} existingLibraryItemId
|
* @param {string} existingLibraryItemId
|
||||||
*/
|
*/
|
||||||
constructor(bookMetadata, audioFiles, ebookFileScanData, libraryItemData, libraryScan, existingLibraryItemId) {
|
constructor(bookMetadata, audioFiles, ebookFileScanData, libraryItemData, libraryScan, existingLibraryItemId) {
|
||||||
this.bookMetadata = bookMetadata
|
this.bookMetadata = bookMetadata
|
||||||
@@ -785,8 +787,8 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('../models/LibraryItem')} libraryItem
|
* @param {import('../models/LibraryItem')} libraryItem
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {LibraryScan} libraryScan
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
@@ -805,12 +807,12 @@ class BookScanner {
|
|||||||
|
|
||||||
const jsonObject = {
|
const jsonObject = {
|
||||||
tags: libraryItem.media.tags || [],
|
tags: libraryItem.media.tags || [],
|
||||||
chapters: libraryItem.media.chapters?.map(c => ({ ...c })) || [],
|
chapters: libraryItem.media.chapters?.map((c) => ({ ...c })) || [],
|
||||||
title: libraryItem.media.title,
|
title: libraryItem.media.title,
|
||||||
subtitle: libraryItem.media.subtitle,
|
subtitle: libraryItem.media.subtitle,
|
||||||
authors: libraryItem.media.authors.map(a => a.name),
|
authors: libraryItem.media.authors.map((a) => a.name),
|
||||||
narrators: libraryItem.media.narrators,
|
narrators: libraryItem.media.narrators,
|
||||||
series: libraryItem.media.series.map(se => {
|
series: libraryItem.media.series.map((se) => {
|
||||||
const sequence = se.bookSeries?.sequence || ''
|
const sequence = se.bookSeries?.sequence || ''
|
||||||
if (!sequence) return se.name
|
if (!sequence) return se.name
|
||||||
return `${se.name} #${sequence}`
|
return `${se.name} #${sequence}`
|
||||||
@@ -826,70 +828,75 @@ class BookScanner {
|
|||||||
explicit: !!libraryItem.media.explicit,
|
explicit: !!libraryItem.media.explicit,
|
||||||
abridged: !!libraryItem.media.abridged
|
abridged: !!libraryItem.media.abridged
|
||||||
}
|
}
|
||||||
return fsExtra.writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2)).then(async () => {
|
return fsExtra
|
||||||
// Add metadata.json to libraryFiles array if it is new
|
.writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2))
|
||||||
let metadataLibraryFile = libraryItem.libraryFiles.find(lf => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
.then(async () => {
|
||||||
if (storeMetadataWithItem) {
|
// Add metadata.json to libraryFiles array if it is new
|
||||||
if (!metadataLibraryFile) {
|
let metadataLibraryFile = libraryItem.libraryFiles.find((lf) => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
||||||
const newLibraryFile = new LibraryFile()
|
if (storeMetadataWithItem) {
|
||||||
await newLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`)
|
if (!metadataLibraryFile) {
|
||||||
metadataLibraryFile = newLibraryFile.toJSON()
|
const newLibraryFile = new LibraryFile()
|
||||||
libraryItem.libraryFiles.push(metadataLibraryFile)
|
await newLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`)
|
||||||
} else {
|
metadataLibraryFile = newLibraryFile.toJSON()
|
||||||
const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath)
|
libraryItem.libraryFiles.push(metadataLibraryFile)
|
||||||
if (fileTimestamps) {
|
} else {
|
||||||
metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs
|
const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath)
|
||||||
metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs
|
if (fileTimestamps) {
|
||||||
metadataLibraryFile.metadata.size = fileTimestamps.size
|
metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs
|
||||||
metadataLibraryFile.ino = fileTimestamps.ino
|
metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs
|
||||||
|
metadataLibraryFile.metadata.size = fileTimestamps.size
|
||||||
|
metadataLibraryFile.ino = fileTimestamps.ino
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const libraryItemDirTimestamps = await getFileTimestampsWithIno(libraryItem.path)
|
||||||
|
if (libraryItemDirTimestamps) {
|
||||||
|
libraryItem.mtime = libraryItemDirTimestamps.mtimeMs
|
||||||
|
libraryItem.ctime = libraryItemDirTimestamps.ctimeMs
|
||||||
|
let size = 0
|
||||||
|
libraryItem.libraryFiles.forEach((lf) => (size += !isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
|
||||||
|
libraryItem.size = size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const libraryItemDirTimestamps = await getFileTimestampsWithIno(libraryItem.path)
|
|
||||||
if (libraryItemDirTimestamps) {
|
|
||||||
libraryItem.mtime = libraryItemDirTimestamps.mtimeMs
|
|
||||||
libraryItem.ctime = libraryItemDirTimestamps.ctimeMs
|
|
||||||
let size = 0
|
|
||||||
libraryItem.libraryFiles.forEach((lf) => size += (!isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
|
|
||||||
libraryItem.size = size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Success saving abmetadata to "${metadataFilePath}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Success saving abmetadata to "${metadataFilePath}"`)
|
||||||
|
|
||||||
return metadataLibraryFile
|
return metadataLibraryFile
|
||||||
}).catch((error) => {
|
})
|
||||||
libraryScan.addLog(LogLevel.ERROR, `Failed to save json file at "${metadataFilePath}"`, error)
|
.catch((error) => {
|
||||||
return null
|
libraryScan.addLog(LogLevel.ERROR, `Failed to save json file at "${metadataFilePath}"`, error)
|
||||||
})
|
return null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check authors that were removed from a book and remove them if they no longer have any books
|
* Check authors that were removed from a book and remove them if they no longer have any books
|
||||||
* keep authors without books that have a asin, description or imagePath
|
* keep authors without books that have a asin, description or imagePath
|
||||||
* @param {string} libraryId
|
* @param {string} libraryId
|
||||||
* @param {import('./ScanLogger')} scanLogger
|
* @param {import('./ScanLogger')} scanLogger
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
async checkAuthorsRemovedFromBooks(libraryId, scanLogger) {
|
async checkAuthorsRemovedFromBooks(libraryId, scanLogger) {
|
||||||
const bookAuthorsToRemove = (await Database.authorModel.findAll({
|
const bookAuthorsToRemove = (
|
||||||
where: [
|
await Database.authorModel.findAll({
|
||||||
{
|
where: [
|
||||||
id: scanLogger.authorsRemovedFromBooks,
|
{
|
||||||
asin: {
|
id: scanLogger.authorsRemovedFromBooks,
|
||||||
[sequelize.Op.or]: [null, ""]
|
asin: {
|
||||||
|
[sequelize.Op.or]: [null, '']
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
[sequelize.Op.or]: [null, '']
|
||||||
|
},
|
||||||
|
imagePath: {
|
||||||
|
[sequelize.Op.or]: [null, '']
|
||||||
|
}
|
||||||
},
|
},
|
||||||
description: {
|
sequelize.where(sequelize.literal('(SELECT count(*) FROM bookAuthors ba WHERE ba.authorId = author.id)'), 0)
|
||||||
[sequelize.Op.or]: [null, ""]
|
],
|
||||||
},
|
attributes: ['id'],
|
||||||
imagePath: {
|
raw: true
|
||||||
[sequelize.Op.or]: [null, ""]
|
})
|
||||||
}
|
).map((au) => au.id)
|
||||||
},
|
|
||||||
sequelize.where(sequelize.literal('(SELECT count(*) FROM bookAuthors ba WHERE ba.authorId = author.id)'), 0)
|
|
||||||
],
|
|
||||||
attributes: ['id'],
|
|
||||||
raw: true
|
|
||||||
})).map(au => au.id)
|
|
||||||
if (bookAuthorsToRemove.length) {
|
if (bookAuthorsToRemove.length) {
|
||||||
await Database.authorModel.destroy({
|
await Database.authorModel.destroy({
|
||||||
where: {
|
where: {
|
||||||
@@ -907,21 +914,23 @@ class BookScanner {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check series that were removed from books and remove them if they no longer have any books
|
* Check series that were removed from books and remove them if they no longer have any books
|
||||||
* @param {string} libraryId
|
* @param {string} libraryId
|
||||||
* @param {import('./ScanLogger')} scanLogger
|
* @param {import('./ScanLogger')} scanLogger
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
async checkSeriesRemovedFromBooks(libraryId, scanLogger) {
|
async checkSeriesRemovedFromBooks(libraryId, scanLogger) {
|
||||||
const bookSeriesToRemove = (await Database.seriesModel.findAll({
|
const bookSeriesToRemove = (
|
||||||
where: [
|
await Database.seriesModel.findAll({
|
||||||
{
|
where: [
|
||||||
id: scanLogger.seriesRemovedFromBooks
|
{
|
||||||
},
|
id: scanLogger.seriesRemovedFromBooks
|
||||||
sequelize.where(sequelize.literal('(SELECT count(*) FROM bookSeries bs WHERE bs.seriesId = series.id)'), 0)
|
},
|
||||||
],
|
sequelize.where(sequelize.literal('(SELECT count(*) FROM bookSeries bs WHERE bs.seriesId = series.id)'), 0)
|
||||||
attributes: ['id'],
|
],
|
||||||
raw: true
|
attributes: ['id'],
|
||||||
})).map(se => se.id)
|
raw: true
|
||||||
|
})
|
||||||
|
).map((se) => se.id)
|
||||||
if (bookSeriesToRemove.length) {
|
if (bookSeriesToRemove.length) {
|
||||||
await Database.seriesModel.destroy({
|
await Database.seriesModel.destroy({
|
||||||
where: {
|
where: {
|
||||||
@@ -938,11 +947,11 @@ class BookScanner {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Search cover provider for matching cover
|
* Search cover provider for matching cover
|
||||||
* @param {string} libraryItemId
|
* @param {string} libraryItemId
|
||||||
* @param {string} libraryItemPath null if book isFile
|
* @param {string} libraryItemPath null if book isFile
|
||||||
* @param {string} title
|
* @param {string} title
|
||||||
* @param {string} author
|
* @param {string} author
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {LibraryScan} libraryScan
|
||||||
* @returns {Promise<string>} path to downloaded cover or null if no cover found
|
* @returns {Promise<string>} path to downloaded cover or null if no cover found
|
||||||
*/
|
*/
|
||||||
async searchForCover(libraryItemId, libraryItemPath, title, author, libraryScan) {
|
async searchForCover(libraryItemId, libraryItemPath, title, author, libraryScan) {
|
||||||
@@ -956,7 +965,6 @@ class BookScanner {
|
|||||||
|
|
||||||
// If the first cover result fails, attempt to download the second
|
// If the first cover result fails, attempt to download the second
|
||||||
for (let i = 0; i < results.length && i < 2; i++) {
|
for (let i = 0; i < results.length && i < 2; i++) {
|
||||||
|
|
||||||
// Downloads and updates the book cover
|
// Downloads and updates the book cover
|
||||||
const result = await CoverManager.downloadCoverFromUrlNew(results[i], libraryItemId, libraryItemPath)
|
const result = await CoverManager.downloadCoverFromUrlNew(results[i], libraryItemId, libraryItemPath)
|
||||||
|
|
||||||
@@ -970,4 +978,4 @@ class BookScanner {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = new BookScanner()
|
module.exports = new BookScanner()
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ const { findMatchingEpisodesInFeed, getPodcastFeed } = require('../utils/podcast
|
|||||||
const BookFinder = require('../finders/BookFinder')
|
const BookFinder = require('../finders/BookFinder')
|
||||||
const PodcastFinder = require('../finders/PodcastFinder')
|
const PodcastFinder = require('../finders/PodcastFinder')
|
||||||
const LibraryScan = require('./LibraryScan')
|
const LibraryScan = require('./LibraryScan')
|
||||||
const Series = require('../objects/entities/Series')
|
|
||||||
const LibraryScanner = require('./LibraryScanner')
|
const LibraryScanner = require('./LibraryScanner')
|
||||||
const CoverManager = require('../managers/CoverManager')
|
const CoverManager = require('../managers/CoverManager')
|
||||||
const TaskManager = require('../managers/TaskManager')
|
const TaskManager = require('../managers/TaskManager')
|
||||||
@@ -209,6 +208,7 @@ class Scanner {
|
|||||||
if (!author) {
|
if (!author) {
|
||||||
author = await Database.authorModel.create({
|
author = await Database.authorModel.create({
|
||||||
name: authorName,
|
name: authorName,
|
||||||
|
lastFirst: Database.authorModel.getLastFirst(authorName),
|
||||||
libraryId: libraryItem.libraryId
|
libraryId: libraryItem.libraryId
|
||||||
})
|
})
|
||||||
SocketAuthority.emitter('author_added', author.toOldJSON())
|
SocketAuthority.emitter('author_added', author.toOldJSON())
|
||||||
@@ -225,14 +225,16 @@ class Scanner {
|
|||||||
if (!Array.isArray(matchData.series)) matchData.series = [{ series: matchData.series, sequence: matchData.sequence }]
|
if (!Array.isArray(matchData.series)) matchData.series = [{ series: matchData.series, sequence: matchData.sequence }]
|
||||||
const seriesPayload = []
|
const seriesPayload = []
|
||||||
for (const seriesMatchItem of matchData.series) {
|
for (const seriesMatchItem of matchData.series) {
|
||||||
let seriesItem = await Database.seriesModel.getOldByNameAndLibrary(seriesMatchItem.series, libraryItem.libraryId)
|
let seriesItem = await Database.seriesModel.getByNameAndLibrary(seriesMatchItem.series, libraryItem.libraryId)
|
||||||
if (!seriesItem) {
|
if (!seriesItem) {
|
||||||
seriesItem = new Series()
|
seriesItem = await Database.seriesModel.create({
|
||||||
seriesItem.setData({ name: seriesMatchItem.series }, libraryItem.libraryId)
|
name: seriesMatchItem.series,
|
||||||
await Database.createSeries(seriesItem)
|
nameIgnorePrefix: getTitleIgnorePrefix(seriesMatchItem.series),
|
||||||
|
libraryId
|
||||||
|
})
|
||||||
// Update filter data
|
// Update filter data
|
||||||
Database.addSeriesToFilterData(libraryItem.libraryId, seriesItem.name, seriesItem.id)
|
Database.addSeriesToFilterData(libraryItem.libraryId, seriesItem.name, seriesItem.id)
|
||||||
SocketAuthority.emitter('series_added', seriesItem.toJSON())
|
SocketAuthority.emitter('series_added', seriesItem.toOldJSON())
|
||||||
}
|
}
|
||||||
seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.sequence))
|
seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.sequence))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ module.exports = {
|
|||||||
* @param {import('../../models/User')} user
|
* @param {import('../../models/User')} user
|
||||||
* @param {string[]} include
|
* @param {string[]} include
|
||||||
* @param {number} limit
|
* @param {number} limit
|
||||||
* @returns {{ series:import('../../objects/entities/Series')[], count:number}}
|
* @returns {{ series:any[], count:number}}
|
||||||
*/
|
*/
|
||||||
async getSeriesMostRecentlyAdded(library, user, include, limit) {
|
async getSeriesMostRecentlyAdded(library, user, include, limit) {
|
||||||
if (!library.isBook) return { series: [], count: 0 }
|
if (!library.isBook) return { series: [], count: 0 }
|
||||||
@@ -276,7 +276,7 @@ module.exports = {
|
|||||||
|
|
||||||
const allOldSeries = []
|
const allOldSeries = []
|
||||||
for (const s of series) {
|
for (const s of series) {
|
||||||
const oldSeries = s.getOldSeries().toJSON()
|
const oldSeries = s.toOldJSON()
|
||||||
|
|
||||||
if (s.feeds?.length) {
|
if (s.feeds?.length) {
|
||||||
oldSeries.rssFeed = Database.feedModel.getOldFeed(s.feeds[0]).toJSONMinified()
|
oldSeries.rssFeed = Database.feedModel.getOldFeed(s.feeds[0]).toJSONMinified()
|
||||||
|
|||||||
@@ -954,12 +954,12 @@ module.exports = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get library items for series
|
* Get library items for series
|
||||||
* @param {import('../../objects/entities/Series')} oldSeries
|
* @param {import('../../models/Series')} series
|
||||||
* @param {import('../../models/User')} [user]
|
* @param {import('../../models/User')} [user]
|
||||||
* @returns {Promise<import('../../objects/LibraryItem')[]>}
|
* @returns {Promise<import('../../objects/LibraryItem')[]>}
|
||||||
*/
|
*/
|
||||||
async getLibraryItemsForSeries(oldSeries, user) {
|
async getLibraryItemsForSeries(series, user) {
|
||||||
const { libraryItems } = await this.getFilteredLibraryItems(oldSeries.libraryId, user, 'series', oldSeries.id, null, null, false, [], null, null)
|
const { libraryItems } = await this.getFilteredLibraryItems(series.libraryId, user, 'series', series.id, null, null, false, [], null, null)
|
||||||
return libraryItems.map((li) => Database.libraryItemModel.getOldLibraryItem(li))
|
return libraryItems.map((li) => Database.libraryItemModel.getOldLibraryItem(li))
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1130,7 +1130,7 @@ module.exports = {
|
|||||||
return Database.libraryItemModel.getOldLibraryItem(libraryItem).toJSON()
|
return Database.libraryItemModel.getOldLibraryItem(libraryItem).toJSON()
|
||||||
})
|
})
|
||||||
seriesMatches.push({
|
seriesMatches.push({
|
||||||
series: series.getOldSeries().toJSON(),
|
series: series.toOldJSON(),
|
||||||
books
|
books
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ module.exports = {
|
|||||||
// Map series to old series
|
// Map series to old series
|
||||||
const allOldSeries = []
|
const allOldSeries = []
|
||||||
for (const s of series) {
|
for (const s of series) {
|
||||||
const oldSeries = s.getOldSeries().toJSON()
|
const oldSeries = s.toOldJSON()
|
||||||
|
|
||||||
if (s.dataValues.totalDuration) {
|
if (s.dataValues.totalDuration) {
|
||||||
oldSeries.totalDuration = s.dataValues.totalDuration
|
oldSeries.totalDuration = s.dataValues.totalDuration
|
||||||
|
|||||||
Reference in New Issue
Block a user