diff --git a/client/components/app/StreamContainer.vue b/client/components/app/StreamContainer.vue index e277966e..9777f28e 100644 --- a/client/components/app/StreamContainer.vue +++ b/client/components/app/StreamContainer.vue @@ -378,7 +378,7 @@ export default { } }, streamReady() { - console.log(`[STREAM-CONTAINER] Stream Ready`) + console.log(`[StreamContainer] Stream Ready`) if (this.$refs.audioPlayer) { this.$refs.audioPlayer.setStreamReady() } else { diff --git a/client/components/tables/BackupsTable.vue b/client/components/tables/BackupsTable.vue index eb16dadb..98f017ad 100644 --- a/client/components/tables/BackupsTable.vue +++ b/client/components/tables/BackupsTable.vue @@ -64,13 +64,11 @@ export default { showConfirmApply: false, selectedBackup: null, isBackingUp: false, - processing: false + processing: false, + backups: [] } }, computed: { - backups() { - return this.$store.state.backups || [] - }, userToken() { return this.$store.getters['user/getToken'] } @@ -96,9 +94,8 @@ export default { this.processing = true this.$axios .$delete(`/api/backups/${backup.id}`) - .then((backups) => { - console.log('Backup deleted', backups) - this.$store.commit('setBackups', backups) + .then((data) => { + this.setBackups(data.backups || []) this.$toast.success(this.$strings.ToastBackupDeleteSuccess) this.processing = false }) @@ -117,10 +114,10 @@ export default { this.isBackingUp = true this.$axios .$post('/api/backups') - .then((backups) => { + .then((data) => { this.isBackingUp = false this.$toast.success(this.$strings.ToastBackupCreateSuccess) - this.$store.commit('setBackups', backups) + this.setBackups(data.backups || []) }) .catch((error) => { this.isBackingUp = false @@ -136,9 +133,8 @@ export default { this.$axios .$post('/api/backups/upload', form) - .then((result) => { - console.log('Upload backup result', result) - this.$store.commit('setBackups', result) + .then((data) => { + this.setBackups(data.backups || []) this.$toast.success(this.$strings.ToastBackupUploadSuccess) this.processing = false }) @@ -148,9 +144,29 @@ export default { this.$toast.error(errorMessage) this.processing = false }) + }, + setBackups(backups) { + backups.sort((a, b) => b.createdAt - a.createdAt) + this.backups = backups + }, + loadBackups() { + this.processing = true + this.$axios + .$get('/api/backups') + .then((data) => { + this.setBackups(data.backups || []) + }) + .catch((error) => { + console.error('Failed to load backups', error) + this.$toast.error('Failed to load backups') + }) + .finally(() => { + this.processing = false + }) } }, mounted() { + this.loadBackups() if (this.$route.query.backup) { this.$toast.success('Backup applied successfully') this.$router.replace('/config') diff --git a/client/components/tables/UsersTable.vue b/client/components/tables/UsersTable.vue index c4459b0d..8fb644ea 100644 --- a/client/components/tables/UsersTable.vue +++ b/client/components/tables/UsersTable.vue @@ -26,11 +26,9 @@ {{ user.type }} -
-

Listening: {{ usersOnline[user.id].session.libraryItem.media.metadata.title || '' }}

-
-
-

Last: {{ user.mostRecent.metadata.title }}

+
+

Listening: {{ usersOnline[user.id].session.libraryItem.media.metadata.title || '' }}

+

Last: {{ usersOnline[user.id].mostRecent.media.metadata.title }}

@@ -81,7 +79,7 @@ export default { }, usersOnline() { var usermap = {} - this.$store.state.users.users.forEach((u) => (usermap[u.id] = { online: true, session: u.session })) + this.$store.state.users.usersOnline.forEach((u) => (usermap[u.id] = u)) return usermap } }, diff --git a/client/layouts/default.vue b/client/layouts/default.vue index 4d9d1438..bb93a054 100644 --- a/client/layouts/default.vue +++ b/client/layouts/default.vue @@ -132,14 +132,8 @@ export default { } }) - if (payload.backups && payload.backups.length) { - this.$store.commit('setBackups', payload.backups) - } if (payload.usersOnline) { - this.$store.commit('users/resetUsers') - payload.usersOnline.forEach((user) => { - this.$store.commit('users/updateUser', user) - }) + this.$store.commit('users/setUsersOnline', payload.usersOnline) } this.$eventBus.$emit('socket_init') @@ -286,13 +280,13 @@ export default { } }, userOnline(user) { - this.$store.commit('users/updateUser', user) + this.$store.commit('users/updateUserOnline', user) }, userOffline(user) { - this.$store.commit('users/removeUser', user) + this.$store.commit('users/removeUserOnline', user) }, userStreamUpdate(user) { - this.$store.commit('users/updateUser', user) + this.$store.commit('users/updateUserOnline', user) }, userMediaProgressUpdate(payload) { this.$store.commit('user/updateMediaProgress', payload) @@ -333,6 +327,9 @@ export default { this.$toast.info(toast) } }, + adminMessageEvt(message) { + this.$toast.info(message) + }, initializeSocket() { this.socket = this.$nuxtSocket({ name: process.env.NODE_ENV === 'development' ? 'dev' : 'prod', @@ -345,6 +342,7 @@ export default { this.$root.socket = this.socket console.log('Socket initialized') + // Pre-defined socket events this.socket.on('connect', this.connect) this.socket.on('connect_error', this.connectError) this.socket.on('disconnect', this.disconnect) @@ -353,6 +351,7 @@ export default { this.socket.io.on('reconnect_error', this.reconnectError) this.socket.io.on('reconnect_failed', this.reconnectFailed) + // Event received after authorizing socket this.socket.on('init', this.init) // Stream Listeners @@ -403,6 +402,8 @@ export default { this.socket.on('backup_applied', this.backupApplied) this.socket.on('batch_quickmatch_complete', this.batchQuickMatchComplete) + + this.socket.on('admin_message', this.adminMessageEvt) }, showUpdateToast(versionData) { var ignoreVersion = localStorage.getItem('ignoreVersion') diff --git a/client/store/index.js b/client/store/index.js index 4ddb5a3c..9b27abd4 100644 --- a/client/store/index.js +++ b/client/store/index.js @@ -21,7 +21,6 @@ export const state = () => ({ processingBatch: false, previousPath: '/', showExperimentalFeatures: false, - backups: [], bookshelfBookIds: [], openModal: null, innerModalOpen: false, @@ -245,9 +244,6 @@ export const mutations = { state.showExperimentalFeatures = val localStorage.setItem('experimental', val ? 1 : 0) }, - setBackups(state, val) { - state.backups = val.sort((a, b) => b.createdAt - a.createdAt) - }, setOpenModal(state, val) { state.openModal = val }, diff --git a/client/store/users.js b/client/store/users.js index 131b7d8c..2439ee31 100644 --- a/client/store/users.js +++ b/client/store/users.js @@ -1,11 +1,11 @@ export const state = () => ({ - users: [] + usersOnline: [] }) export const getters = { getIsUserOnline: state => id => { - return state.users.find(u => u.id === id) + return state.usersOnline.find(u => u.id === id) } } @@ -14,18 +14,18 @@ export const actions = { } export const mutations = { - resetUsers(state) { - state.users = [] + setUsersOnline(state, usersOnline) { + state.usersOnline = usersOnline }, - updateUser(state, user) { - var index = state.users.findIndex(u => u.id === user.id) + updateUserOnline(state, user) { + var index = state.usersOnline.findIndex(u => u.id === user.id) if (index >= 0) { - state.users.splice(index, 1, user) + state.usersOnline.splice(index, 1, user) } else { - state.users.push(user) + state.usersOnline.push(user) } }, - removeUser(state, user) { - state.users = state.users.filter(u => u.id !== user.id) + removeUserOnline(state, user) { + state.usersOnline = state.usersOnline.filter(u => u.id !== user.id) } } \ No newline at end of file diff --git a/client/strings/pl.json b/client/strings/pl.json index 05b3d17d..a9fdffb0 100644 --- a/client/strings/pl.json +++ b/client/strings/pl.json @@ -13,7 +13,7 @@ "ButtonCheckAndDownloadNewEpisodes": "Sprawdź i pobierz nowe odcinki", "ButtonChooseAFolder": "Wybierz folder", "ButtonChooseFiles": "Wybierz pliki", - "ButtonClearFilter": "Clear Filter", + "ButtonClearFilter": "Wyczyść filtr", "ButtonCloseFeed": "Zamknij kanał", "ButtonCollections": "Kolekcje", "ButtonConfigureScanner": "Configure Scanner", @@ -44,8 +44,8 @@ "ButtonPurgeAllCache": "Wyczyść dane tymczasowe", "ButtonPurgeItemsCache": "Wyczyść dane tymczasowe pozycji", "ButtonPurgeMediaProgress": "Wyczyść postęp", - "ButtonQueueAddItem": "Add to queue", - "ButtonQueueRemoveItem": "Remove from queue", + "ButtonQueueAddItem": "Dodaj do kolejki", + "ButtonQueueRemoveItem": "Usuń z kolejki", "ButtonQuickMatch": "Szybkie dopasowanie", "ButtonRead": "Czytaj", "ButtonRemove": "Usuń", @@ -68,7 +68,7 @@ "ButtonShow": "Pokaż", "ButtonStartM4BEncode": "Eksportuj jako plik M4B", "ButtonStartMetadataEmbed": "Osadź metadane", - "ButtonSubmit": "Zgłoś", + "ButtonSubmit": "Zaloguj", "ButtonUpload": "Wgraj", "ButtonUploadBackup": "Wgraj kopię zapasową", "ButtonUploadCover": "Wgraj okładkę", @@ -96,7 +96,7 @@ "HeaderLastListeningSession": "Ostatnio odtwarzana sesja", "HeaderLatestEpisodes": "Najnowsze odcinki", "HeaderLibraries": "Biblioteki", - "HeaderLibraryFiles": "Library Files", + "HeaderLibraryFiles": "Pliki w bibliotece", "HeaderLibraryStats": "Statystyki biblioteki", "HeaderListeningSessions": "Sesje słuchania", "HeaderListeningStats": "Statystyki odtwarzania", @@ -144,14 +144,14 @@ "LabelAccountTypeGuest": "Gość", "LabelAccountTypeUser": "Użytkownik", "LabelActivity": "Aktywność", - "LabelAddedAt": "Added At", + "LabelAddedAt": "Dodano", "LabelAddToCollection": "Dodaj do kolekcji", "LabelAddToCollectionBatch": "Dodaj {0} książki do kolekcji", "LabelAll": "All", "LabelAllUsers": "Wszyscy użytkownicy", "LabelAuthor": "Autor", - "LabelAuthorFirstLast": "Author (First Last)", - "LabelAuthorLastFirst": "Author (Last, First)", + "LabelAuthorFirstLast": "Autor (Rosnąco)", + "LabelAuthorLastFirst": "Author (Malejąco)", "LabelAuthors": "Autorzy", "LabelAutoDownloadEpisodes": "Automatyczne pobieranie odcinków", "LabelBackToUser": "Powrót", @@ -165,7 +165,7 @@ "LabelChangePassword": "Zmień hasło", "LabelChaptersFound": "Znalezione rozdziały", "LabelChapterTitle": "Tytuł rozdziału", - "LabelClosePlayer": "Close player", + "LabelClosePlayer": "Zamknij odtwarzacz", "LabelCollapseSeries": "Podsumuj serię", "LabelCollections": "Kolekcje", "LabelComplete": "Ukończone", @@ -198,15 +198,15 @@ "LabelExplicit": "Nieprzyzwoite", "LabelFeedURL": "URL kanału", "LabelFile": "Plik", - "LabelFileBirthtime": "File Birthtime", - "LabelFileModified": "File Modified", + "LabelFileBirthtime": "Data utworzenia pliku", + "LabelFileModified": "Data modyfikacji pliku", "LabelFilename": "Nazwa pliku", "LabelFilterByUser": "Filtruj według danego użytkownika", "LabelFindEpisodes": "Znajdź odcinki", "LabelFinished": "Zakończone", "LabelFolder": "Folder", "LabelFolders": "Foldery", - "LabelGenre": "Genre", + "LabelGenre": "Gatunek", "LabelGenres": "Gatunki", "LabelHardDeleteFile": "Usuń trwale plik", "LabelHour": "Godzina", @@ -215,14 +215,14 @@ "LabelIncomplete": "Nieukończone", "LabelInProgress": "W trakcie", "LabelInterval": "Interwał", - "LabelIntervalCustomDailyWeekly": "Custom daily/weekly", - "LabelIntervalEvery12Hours": "Every 12 hours", - "LabelIntervalEvery15Minutes": "Every 15 minutes", - "LabelIntervalEvery2Hours": "Every 2 hours", - "LabelIntervalEvery30Minutes": "Every 30 minutes", - "LabelIntervalEvery6Hours": "Every 6 hours", - "LabelIntervalEveryDay": "Every day", - "LabelIntervalEveryHour": "Every hour", + "LabelIntervalCustomDailyWeekly": "Niestandardowy dzienny/tygodniowy", + "LabelIntervalEvery12Hours": "Co 12 godzin", + "LabelIntervalEvery15Minutes": "Co 15 minut", + "LabelIntervalEvery2Hours": "Co 2 godziny", + "LabelIntervalEvery30Minutes": "Co 30 minut", + "LabelIntervalEvery6Hours": "Co 6 godzin", + "LabelIntervalEveryDay": "Każdego dnia", + "LabelIntervalEveryHour": "Każdej godziny", "LabelInvalidParts": "Nieprawidłowe części", "LabelItem": "Pozycja", "LabelLanguage": "Język", @@ -238,8 +238,8 @@ "LabelLimit": "Limit", "LabelListenAgain": "Słuchaj ponownie", "LabelLogLevelDebug": "Debug", - "LabelLogLevelInfo": "Info", - "LabelLogLevelWarn": "Warn", + "LabelLogLevelInfo": "Informacja", + "LabelLogLevelWarn": "Ostrzeżenie", "LabelLookForNewEpisodesAfterDate": "Szukaj nowych odcinków po dacie", "LabelMarkSeries": "Oznacz serię", "LabelMediaPlayer": "Odtwarzacz", @@ -268,9 +268,9 @@ "LabelNotificationsMaxQueueSize": "Maksymalny rozmiar kolejki dla powiadomień", "LabelNotificationsMaxQueueSizeHelp": "Zdarzenia są ograniczone do 1 na sekundę. Zdarzenia będą ignorowane jeśli kolejka ma maksymalny rozmiar. Zapobiega to spamowaniu powiadomieniami.", "LabelNotificationTitleTemplate": "Szablon tytułu powiadmienia", - "LabelNotStarted": "Not Started", - "LabelNumberOfBooks": "Number of Books", - "LabelNumberOfEpisodes": "# of Episodes", + "LabelNotStarted": "Nie rozpoęczto", + "LabelNumberOfBooks": "Liczba książek", + "LabelNumberOfEpisodes": "# odcinków", "LabelOpenRSSFeed": "Otwórz kanał RSS", "LabelPassword": "Hasło", "LabelPath": "Ścieżka", @@ -296,7 +296,7 @@ "LabelRegion": "Region", "LabelReleaseDate": "Data wydania", "LabelRemoveCover": "Remove cover", - "LabelRSSFeedOpen": "RSS Feed Open", + "LabelRSSFeedOpen": "RSS Feed otwarty", "LabelRSSFeedSlug": "RSS Feed Slug", "LabelRSSFeedURL": "URL kanały RSS", "LabelSearchTerm": "Wyszukiwanie frazy", @@ -306,7 +306,7 @@ "LabelSequence": "Kolejność", "LabelSeries": "Serie", "LabelSeriesName": "Nazwy serii", - "LabelSeriesProgress": "Series Progress", + "LabelSeriesProgress": "Postęp w serii", "LabelSettingsBookshelfViewHelp": "Widok półki z ksiązkami", "LabelSettingsChromecastSupport": "Wsparcie Chromecast", "LabelSettingsDateFormat": "Format daty", @@ -343,7 +343,7 @@ "LabelSettingsStoreMetadataWithItemHelp": "Domyślnie metadane są przechowywane w folderze /metadata/items, włączenie tej opcji spowoduje, że okładka będzie przechowywana w folderze ksiązki. Tylko jedna okładka o nazwie pliku \"cover\" będzie przechowywana. Rozszerzenie pliku metadanych: .abs", "LabelShowAll": "Pokaż wszystko", "LabelSize": "Rozmiar", - "LabelSleepTimer": "Sleep timer", + "LabelSleepTimer": "Wyłącznik czasowy", "LabelStart": "Rozpocznij", "LabelStarted": "Rozpoczęty", "LabelStartedAt": "Rozpoczęto", @@ -360,8 +360,8 @@ "LabelStatsItemsInLibrary": "Pozycje w bibliotece", "LabelStatsMinutes": "Minuty", "LabelStatsMinutesListening": "Minuty odtwarzania", - "LabelStatsOverallDays": "Overall Days", - "LabelStatsOverallHours": "Overall Hours", + "LabelStatsOverallDays": "Całkowity czas (dni)", + "LabelStatsOverallHours": "Całkowity czas (godziny)", "LabelStatsWeekListening": "Tydzień odtwarzania", "LabelSubtitle": "Podtytuł", "LabelSupportedFileTypes": "Obsługiwane typy plików", @@ -373,13 +373,13 @@ "LabelTimeRemaining": "Pozostało {0}", "LabelTimeToShift": "Czas do przesunięcia w sekundach", "LabelTitle": "Tytuł", - "LabelToolsEmbedMetadata": "Embed Metadata", - "LabelToolsEmbedMetadataDescription": "Embed metadata into audio files including cover image and chapters.", - "LabelToolsMakeM4b": "Make M4B Audiobook File", - "LabelToolsMakeM4bDescription": "Generate a .M4B audiobook file with embedded metadata, cover image, and chapters.", - "LabelToolsSplitM4b": "Split M4B to MP3's", - "LabelToolsSplitM4bDescription": "Create MP3's from an M4B split by chapters with embedded metadata, cover image, and chapters.", - "LabelTotalDuration": "Total Duration", + "LabelToolsEmbedMetadata": "Załącz metadane", + "LabelToolsEmbedMetadataDescription": "Załącz metadane do plików audio (okładkę oraz znaczniki rozdziałów)", + "LabelToolsMakeM4b": "Generuj plik M4B", + "LabelToolsMakeM4bDescription": "Tworzy plik w formacie .M4B, który zawiera metadane, okładkę oraz rozdziały.", + "LabelToolsSplitM4b": "Podziel plik .M4B na pliki .MP3", + "LabelToolsSplitM4bDescription": "Podziel plik .M4B na pliki .MP3 na rozdziały z załączonymi metadanymi oraz okładką.", + "LabelTotalDuration": "TCałkowita długość", "LabelTotalTimeListened": "Całkowity czas odtwarzania", "LabelTrackFromFilename": "Ścieżka z nazwy pliku", "LabelTrackFromMetadata": "Ścieżka z metadanych", @@ -387,33 +387,33 @@ "LabelUnknown": "Nieznany", "LabelUpdateCover": "Zaktalizuj odkładkę", "LabelUpdateCoverHelp": "Umożliwienie nadpisania istniejących okładek dla wybranych książek w przypadku znalezienia dopasowania", - "LabelUpdatedAt": "Zaktualizaowano", + "LabelUpdatedAt": "Zaktualizowano", "LabelUpdateDetails": "Zaktualizuj szczegóły", "LabelUpdateDetailsHelp": "Umożliwienie nadpisania istniejących szczegółów dla wybranych książek w przypadku znalezienia dopasowania", "LabelUploaderDragAndDrop": "Przeciągnij i puść foldery lub pliki", "LabelUploaderDropFiles": "Puść pliki", - "LabelUseChapterTrack": "Use chapter track", + "LabelUseChapterTrack": "Użyj ścieżki rozdziału", "LabelUseFullTrack": "Użycie ścieżki rozdziału", "LabelUser": "Użytkownik", "LabelUsername": "Nazwa użytkownika", "LabelValue": "Wartość", "LabelVersion": "Wersja", - "LabelViewBookmarks": "View bookmarks", - "LabelViewChapters": "View chapters", - "LabelViewQueue": "View player queue", - "LabelVolume": "Volume", + "LabelViewBookmarks": "Wyświetlaj zakładki", + "LabelViewChapters": "Wyświetlaj rozdziały", + "LabelViewQueue": "Wyświetlaj kolejkę odtwarzania", + "LabelVolume": "Głośność", "LabelWeekdaysToRun": "Dni tygodnia", "LabelYourAudiobookDuration": "Czas trwania audiobooka", "LabelYourBookmarks": "Twoje zakładki", "LabelYourProgress": "Twój postęp", - "MessageAppriseDescription": "To use this feature you will need to have an instance of Apprise API running or an api that will handle those same requests.
The Apprise API Url should be the full URL path to send the notification, e.g., if your API instance is served at http://192.168.1.1:8337 then you would put http://192.168.1.1:8337/notify.", + "MessageAppriseDescription": "Aby użyć tej funkcji, konieczne jest posiadanie instancji Apprise API albo innego rozwiązania, które obsługuje schemat zapytań Apprise.
URL do interfejsu API powinno być całkowitą ścieżką, np., jeśli Twoje API do powiadomień jest dostępne pod adresem http://192.168.1.1:8337 to wpisany tutaj URL powinien mieć postać: http://192.168.1.1:8337/notify.", "MessageBackupsDescription": "Kopie zapasowe obejmują użytkowników, postępy użytkowników, szczegóły pozycji biblioteki, ustawienia serwera i obrazy przechowywane w", "MessageBackupsNote": "Kopie zapasowe nie obejmują żadnych plików przechowywanych w folderach biblioteki.", "MessageBatchQuickMatchDescription": "Quick Match będzie próbował dodać brakujące okładki i metadane dla wybranych elementów. Włącz poniższe opcje, aby umożliwić Quick Match nadpisanie istniejących okładek i/lub metadanych.", - "MessageBookshelfNoCollections": "You haven't made any collections yet", - "MessageBookshelfNoResultsForFilter": "No Results for filter \"{0}: {1}\"", - "MessageBookshelfNoRSSFeeds": "No RSS feeds are open", - "MessageBookshelfNoSeries": "You have no series", + "MessageBookshelfNoCollections": "Nie posiadasz jeszcze żadnych kolekcji", + "MessageBookshelfNoResultsForFilter": "Nie znaleziono żadnych pozycji przy aktualnym filtrowaniu \"{0}: {1}\"", + "MessageBookshelfNoRSSFeeds": "Nie posiadasz żadnych otwartych feedów RSS", + "MessageBookshelfNoSeries": "Nie masz jeszcze żadnych serii", "MessageChapterEndIsAfter": "Koniec rozdziału następuje po zakończeniu audiobooka", "MessageChapterStartIsAfter": "Początek rozdziału następuje po zakończeniu audiobooka", "MessageCheckingCron": "Sprawdzanie cron...", @@ -456,7 +456,7 @@ "MessageNoEpisodes": "Brak odcinków", "MessageNoFoldersAvailable": "Brak dostępnych folderów", "MessageNoGenres": "Brak gatunków", - "MessageNoIssues": "No Issues", + "MessageNoIssues": "Brak problemów", "MessageNoItems": "Brak elementów", "MessageNoItemsFound": "Nie znaleziono żadnych elemntów", "MessageNoListeningSessions": "Brak sesji odtwarzania", @@ -466,16 +466,16 @@ "MessageNoPodcastsFound": "Nie znaleziono podcastów", "MessageNoResults": "Brak wyników", "MessageNoSearchResultsFor": "Brak wyników wyszukiwania dla \"{0}\"", - "MessageNotYetImplemented": "Not yet implemented", + "MessageNotYetImplemented": "Jeszcze nie zaimplementowane", "MessageNoUpdateNecessary": "Brak konieczności aktualizacji", "MessageNoUpdatesWereNecessary": "Brak aktualizacji", - "MessageOr": "or", - "MessagePauseChapter": "Pause chapter playback", - "MessagePlayChapter": "Listen to beginning of chapter", + "MessageOr": "lub", + "MessagePauseChapter": "Zatrzymaj odtwarzanie rozdziały", + "MessagePlayChapter": "Rozpocznij odtwarzanie od początku rozdziału", "MessagePodcastHasNoRSSFeedForMatching": "Podcast nie ma adresu url kanału RSS, który mógłby zostać użyty do dopasowania", "MessageQuickMatchDescription": "Wypełnij puste informacje i okładkę pierwszym wynikiem dopasowania z '{0}'. Nie nadpisuje szczegółów, chyba że włączone jest ustawienie serwera 'Preferuj dopasowane metadane'.", "MessageRemoveAllItemsWarning": "UWAGA! Ta akcja usunie wszystkie elementy biblioteki z bazy danych, w tym wszystkie aktualizacje lub dopasowania, które zostały wykonane. Pliki pozostaną niezmienione. Czy jesteś pewien?", - "MessageRemoveChapter": "Remove chapter", + "MessageRemoveChapter": "Usuń rozdział", "MessageRemoveEpisodes": "Usuń {0} odcinków", "MessageRemoveUserWarning": "Czy na pewno chcesz trwale usunąć użytkownika \"{0}\"?", "MessageReportBugsAndContribute": "Zgłoś błędy, pomysły i pomóż rozwijać aplikację na", @@ -490,12 +490,12 @@ "MessageUploading": "Przesyłanie...", "MessageValidCronExpression": "Sprawdź wyrażenie CRON", "MessageWatcherIsDisabledGlobally": "Watcher jest wyłączony globalnie w ustawieniach serwera", - "MessageXLibraryIsEmpty": "{0} Library is empty!", + "MessageXLibraryIsEmpty": "{0} Biblioteka jest pusta!", "MessageYourAudiobookDurationIsLonger": "Czas trwania Twojego audiobooka jest dłuższy niż znaleziony czas trwania", "MessageYourAudiobookDurationIsShorter": "Czas trwania Twojego audiobooka jest krótszy niż znaleziony czas trwania", "NoteChangeRootPassword": "Tylko użytkownik root, może posiadać puste hasło", "NoteChapterEditorTimes": "Uwaga: Czas rozpoczęcia pierwszego rozdziału musi pozostać na poziomie 0:00, a czas rozpoczęcia ostatniego rozdziału nie może przekroczyć czasu trwania audiobooka.", - "NoteFolderPicker": "Note: folders already mapped will not be shown", + "NoteFolderPicker": "Uwaga: dotychczas zmapowane foldery nie zostaną wyświetlone", "NoteFolderPickerDebian": "Uwaga: Wybór folderu w instalcji opartej o system debian nie jest w pełni zaimplementowany. Powinieneś wprowadzić ścieżkę do swojej biblioteki bezpośrednio.", "NoteRSSFeedPodcastAppsHttps": "Ostrzeżenie: Większość aplikacji do obsługi podcastów wymaga, aby adres URL kanału RSS korzystał z protokołu HTTPS.", "NoteRSSFeedPodcastAppsPubDate": "Ostrzeżenie: 1 lub więcej odcinków nie ma daty publikacji. Niektóre aplikacje do słuchania podcastów tego wymagają.", @@ -515,8 +515,8 @@ "ToastAuthorUpdateSuccessNoImageFound": "Autor zaktualizowany (nie znaleziono obrazu)", "ToastBackupCreateFailed": "Nie udało się utworzyć kopii zapasowej", "ToastBackupCreateSuccess": "Utworzono kopię zapasową", - "ToastBackupDeleteFailed": "Failed to delete backup", - "ToastBackupDeleteSuccess": "Nie udało się usunąć kopii zapasowej", + "ToastBackupDeleteFailed": "Nie udało się usunąć kopii zapasowej", + "ToastBackupDeleteSuccess": "Udało się usunąć kopie zapasowej", "ToastBackupRestoreFailed": "Nie udało się przywrócić kopii zapasowej", "ToastBackupUploadFailed": "Nie udało się przesłać kopii zapasowej", "ToastBackupUploadSuccess": "Kopia zapasowa została przesłana", @@ -559,9 +559,9 @@ "ToastRSSFeedCloseSuccess": "Zamknięcie kanału RSS powiodło się", "ToastSessionDeleteFailed": "Nie udało się usunąć sesji", "ToastSessionDeleteSuccess": "Sesja usunięta", - "ToastSocketConnected": "Socket connected", - "ToastSocketDisconnected": "Socket disconnected", - "ToastSocketFailedToConnect": "Socket failed to connect", + "ToastSocketConnected": "Nawiązano połączenie z serwerem", + "ToastSocketDisconnected": "Połączenie z serwerem zostało zamknięte", + "ToastSocketFailedToConnect": "Poączenie z serwerem nie powiodło się", "ToastUserDeleteFailed": "Nie udało się usunąć użytkownika", "ToastUserDeleteSuccess": "Użytkownik usunięty", "WeekdayFriday": "Piątek", diff --git a/server/Db.js b/server/Db.js index 040d3c72..2ebbee2e 100644 --- a/server/Db.js +++ b/server/Db.js @@ -270,23 +270,6 @@ class Db { }) } - updateUserStream(userId, streamId) { - return this.usersDb.update((record) => record.id === userId, (user) => { - user.stream = streamId - return user - }).then((results) => { - Logger.debug(`[DB] Updated user ${results.updated}`) - this.users = this.users.map(u => { - if (u.id === userId) { - u.stream = streamId - } - return u - }) - }).catch((error) => { - Logger.error(`[DB] Update user Failed ${error}`) - }) - } - updateServerSettings() { global.ServerSettings = this.serverSettings.toJSON() return this.updateEntity('settings', this.serverSettings) diff --git a/server/Server.js b/server/Server.js index 7983809f..ef343f17 100644 --- a/server/Server.js +++ b/server/Server.js @@ -1,7 +1,6 @@ const Path = require('path') const express = require('express') const http = require('http') -const SocketIO = require('socket.io') const fs = require('./libs/fsExtra') const fileUpload = require('./libs/expressFileupload') const rateLimit = require('./libs/expressRateLimit') @@ -13,11 +12,11 @@ const dbMigration = require('./utils/dbMigration') const filePerms = require('./utils/filePerms') const Logger = require('./Logger') -// Classes const Auth = require('./Auth') const Watcher = require('./Watcher') const Scanner = require('./scanner/Scanner') const Db = require('./Db') +const SocketAuthority = require('./SocketAuthority') const ApiRouter = require('./routers/ApiRouter') const HlsRouter = require('./routers/HlsRouter') @@ -67,59 +66,30 @@ class Server { this.auth = new Auth(this.db) // Managers - this.taskManager = new TaskManager(this.emitter.bind(this)) - this.notificationManager = new NotificationManager(this.db, this.emitter.bind(this)) - this.backupManager = new BackupManager(this.db, this.emitter.bind(this)) + this.taskManager = new TaskManager() + this.notificationManager = new NotificationManager(this.db) + this.backupManager = new BackupManager(this.db) this.logManager = new LogManager(this.db) this.cacheManager = new CacheManager() - this.abMergeManager = new AbMergeManager(this.db, this.taskManager, this.clientEmitter.bind(this)) - this.playbackSessionManager = new PlaybackSessionManager(this.db, this.emitter.bind(this), this.clientEmitter.bind(this)) + this.abMergeManager = new AbMergeManager(this.db, this.taskManager) + this.playbackSessionManager = new PlaybackSessionManager(this.db) this.coverManager = new CoverManager(this.db, this.cacheManager) - this.podcastManager = new PodcastManager(this.db, this.watcher, this.emitter.bind(this), this.notificationManager) - this.audioMetadataManager = new AudioMetadataMangaer(this.db, this.taskManager, this.emitter.bind(this), this.clientEmitter.bind(this)) - this.rssFeedManager = new RssFeedManager(this.db, this.emitter.bind(this)) + this.podcastManager = new PodcastManager(this.db, this.watcher, this.notificationManager) + this.audioMetadataManager = new AudioMetadataMangaer(this.db, this.taskManager) + this.rssFeedManager = new RssFeedManager(this.db) - this.scanner = new Scanner(this.db, this.coverManager, this.emitter.bind(this)) + this.scanner = new Scanner(this.db, this.coverManager) this.cronManager = new CronManager(this.db, this.scanner, this.podcastManager) // Routers - this.apiRouter = new ApiRouter(this.db, this.auth, this.scanner, this.playbackSessionManager, this.abMergeManager, this.coverManager, this.backupManager, this.watcher, this.cacheManager, this.podcastManager, this.audioMetadataManager, this.rssFeedManager, this.cronManager, this.notificationManager, this.taskManager, this.getUsersOnline.bind(this), this.emitter.bind(this), this.clientEmitter.bind(this)) - this.hlsRouter = new HlsRouter(this.db, this.auth, this.playbackSessionManager, this.emitter.bind(this)) + this.apiRouter = new ApiRouter(this) + this.hlsRouter = new HlsRouter(this.db, this.auth, this.playbackSessionManager) this.staticRouter = new StaticRouter(this.db) Logger.logManager = this.logManager this.server = null this.io = null - - this.clients = {} - } - - getUsersOnline() { - return Object.values(this.clients).filter(c => c.user).map(client => { - return client.user.toJSONForPublic(this.playbackSessionManager.sessions, this.db.libraryItems) - }) - } - - getClientsForUser(userId) { - return Object.values(this.clients).filter(c => c.user && c.user.id === userId) - } - - emitter(ev, data) { - // Logger.debug('EMITTER', ev) - this.io.emit(ev, data) - } - - clientEmitter(userId, ev, data) { - var clients = this.getClientsForUser(userId) - if (!clients.length) { - return Logger.debug(`[Server] clientEmitter - no clients found for user ${userId}`) - } - clients.forEach((client) => { - if (client.socket) { - client.socket.emit(ev, data) - } - }) } authMiddleware(req, res, next) { @@ -130,7 +100,7 @@ class Server { Logger.info('[Server] Init v' + version) await this.playbackSessionManager.removeOrphanStreams() - var previousVersion = await this.db.checkPreviousVersion() // Returns null if same server version + const previousVersion = await this.db.checkPreviousVersion() // Returns null if same server version if (previousVersion) { Logger.debug(`[Server] Upgraded from previous version ${previousVersion}`) } @@ -197,13 +167,13 @@ class Server { // EBook static file routes router.get('/ebook/:library/:folder/*', (req, res) => { - var library = this.db.libraries.find(lib => lib.id === req.params.library) + const library = this.db.libraries.find(lib => lib.id === req.params.library) if (!library) return res.sendStatus(404) - var folder = library.folders.find(fol => fol.id === req.params.folder) + const folder = library.folders.find(fol => fol.id === req.params.folder) if (!folder) return res.status(404).send('Folder not found') - var remainingPath = req.params['0'] - var fullPath = Path.join(folder.fullPath, remainingPath) + const remainingPath = req.params['0'] + const fullPath = Path.join(folder.fullPath, remainingPath) res.sendFile(fullPath) }) @@ -272,58 +242,8 @@ class Server { Logger.info(`Listening on http://${this.Host}:${this.Port}`) }) - this.io = new SocketIO.Server(this.server, { - cors: { - origin: '*', - methods: ["GET", "POST"] - } - }) - this.io.on('connection', (socket) => { - this.clients[socket.id] = { - id: socket.id, - socket, - connected_at: Date.now() - } - socket.sheepClient = this.clients[socket.id] - - Logger.info('[Server] Socket Connected', socket.id) - - socket.on('auth', (token) => this.authenticateSocket(socket, token)) - - // Scanning - socket.on('cancel_scan', this.cancelScan.bind(this)) - - // Logs - socket.on('set_log_listener', (level) => Logger.addSocketListener(socket, level)) - socket.on('remove_log_listener', () => Logger.removeSocketListener(socket.id)) - socket.on('fetch_daily_logs', () => this.logManager.socketRequestDailyLogs(socket)) - - socket.on('ping', () => { - var client = this.clients[socket.id] || {} - var user = client.user || {} - Logger.debug(`[Server] Received ping from socket ${user.username || 'No User'}`) - socket.emit('pong') - }) - - socket.on('disconnect', (reason) => { - Logger.removeSocketListener(socket.id) - - var _client = this.clients[socket.id] - if (!_client) { - Logger.warn(`[Server] Socket ${socket.id} disconnect, no client (Reason: ${reason})`) - } else if (!_client.user) { - Logger.info(`[Server] Unauth socket ${socket.id} disconnected (Reason: ${reason})`) - delete this.clients[socket.id] - } else { - Logger.debug('[Server] User Offline ' + _client.user.username) - this.io.emit('user_offline', _client.user.toJSONForPublic(this.playbackSessionManager.sessions, this.db.libraryItems)) - - const disconnectTime = Date.now() - _client.connected_at - Logger.info(`[Server] Socket ${socket.id} disconnected from client "${_client.user.username}" after ${disconnectTime}ms (Reason: ${reason})`) - delete this.clients[socket.id] - } - }) - }) + // Start listening for socket connections + SocketAuthority.initialize(this) } async initializeServer(req, res) { @@ -342,22 +262,17 @@ class Server { await this.scanner.scanFilesChanged(fileUpdates) } - cancelScan(id) { - Logger.debug('[Server] Cancel scan', id) - this.scanner.setCancelLibraryScan(id) - } - // Remove unused /metadata/items/{id} folders async purgeMetadata() { - var itemsMetadata = Path.join(global.MetadataPath, 'items') + const itemsMetadata = Path.join(global.MetadataPath, 'items') if (!(await fs.pathExists(itemsMetadata))) return - var foldersInItemsMetadata = await fs.readdir(itemsMetadata) + const foldersInItemsMetadata = await fs.readdir(itemsMetadata) - var purged = 0 + let purged = 0 await Promise.all(foldersInItemsMetadata.map(async foldername => { - var hasMatchingItem = this.db.libraryItems.find(ab => ab.id === foldername) + const hasMatchingItem = this.db.libraryItems.find(ab => ab.id === foldername) if (!hasMatchingItem) { - var folderPath = Path.join(itemsMetadata, foldername) + const folderPath = Path.join(itemsMetadata, foldername) Logger.debug(`[Server] Purging unused metadata ${folderPath}`) await fs.remove(folderPath).then(() => { @@ -376,8 +291,8 @@ class Server { // Remove user media progress with items that no longer exist & remove seriesHideFrom that no longer exist async cleanUserData() { for (let i = 0; i < this.db.users.length; i++) { - var _user = this.db.users[i] - var hasUpdated = false + const _user = this.db.users[i] + let hasUpdated = false if (_user.mediaProgress.length) { const lengthBefore = _user.mediaProgress.length _user.mediaProgress = _user.mediaProgress.filter(mp => { @@ -423,68 +338,14 @@ class Server { } logout(req, res) { - var { socketId } = req.body - Logger.info(`[Server] User ${req.user ? req.user.username : 'Unknown'} is logging out with socket ${socketId}`) - - // Strip user and client from client and client socket - if (socketId && this.clients[socketId]) { - var client = this.clients[socketId] - var clientSocket = client.socket - Logger.debug(`[Server] Found user client ${clientSocket.id}, Has user: ${!!client.user}, Socket has client: ${!!clientSocket.sheepClient}`) - - if (client.user) { - Logger.debug('[Server] User Offline ' + client.user.username) - this.io.emit('user_offline', client.user.toJSONForPublic(null, this.db.libraryItems)) - } - - delete this.clients[socketId].user - if (clientSocket && clientSocket.sheepClient) delete this.clients[socketId].socket.sheepClient - } else if (socketId) { - Logger.warn(`[Server] No client for socket ${socketId}`) + if (req.body.socketId) { + Logger.info(`[Server] User ${req.user ? req.user.username : 'Unknown'} is logging out with socket ${req.body.socketId}`) + SocketAuthority.logout(req.body.socketId) } res.sendStatus(200) } - async authenticateSocket(socket, token) { - var user = await this.auth.authenticateUser(token) - if (!user) { - Logger.error('Cannot validate socket - invalid token') - return socket.emit('invalid_token') - } - var client = this.clients[socket.id] - - if (client.user !== undefined) { - Logger.debug(`[Server] Authenticating socket client already has user`, client.user.username) - } - - client.user = user - - if (!client.user.toJSONForBrowser) { - Logger.error('Invalid user...', client.user) - return - } - - Logger.debug(`[Server] User Online ${client.user.username}`) - - this.io.emit('user_online', client.user.toJSONForPublic(this.playbackSessionManager.sessions, this.db.libraryItems)) - - user.lastSeen = Date.now() - await this.db.updateEntity('user', user) - - const initialPayload = { - metadataPath: global.MetadataPath, - configPath: global.ConfigPath, - user: client.user.toJSONForBrowser(), - librariesScanning: this.scanner.librariesScanning, - backups: (this.backupManager.backups || []).map(b => b.toJSON()) - } - if (user.type === 'root') { - initialPayload.usersOnline = this.getUsersOnline() - } - client.socket.emit('init', initialPayload) - } - async stop() { await this.watcher.close() Logger.info('Watcher Closed') diff --git a/server/SocketAuthority.js b/server/SocketAuthority.js new file mode 100644 index 00000000..5488cf74 --- /dev/null +++ b/server/SocketAuthority.js @@ -0,0 +1,200 @@ +const SocketIO = require('socket.io') +const Logger = require('./Logger') + +class SocketAuthority { + constructor() { + this.Server = null + this.io = null + + this.clients = {} + } + + // returns an array of User.toJSONForPublic with `connections` for the # of socket connections + // a user can have many socket connections + getUsersOnline() { + const onlineUsersMap = {} + Object.values(this.clients).filter(c => c.user).forEach(client => { + if (onlineUsersMap[client.user.id]) { + onlineUsersMap[client.user.id].connections++ + } else { + onlineUsersMap[client.user.id] = { + ...client.user.toJSONForPublic(this.Server.playbackSessionManager.sessions, this.Server.db.libraryItems), + connections: 1 + } + } + }) + return Object.values(onlineUsersMap) + } + + getClientsForUser(userId) { + return Object.values(this.clients).filter(c => c.user && c.user.id === userId) + } + + // Emits event to all authorized clients + emitter(evt, data) { + for (const socketId in this.clients) { + if (this.clients[socketId].user) { + this.clients[socketId].socket.emit(evt, data) + } + } + } + + // Emits event to all clients for a specific user + clientEmitter(userId, evt, data) { + const clients = this.getClientsForUser(userId) + if (!clients.length) { + return Logger.debug(`[Server] clientEmitter - no clients found for user ${userId}`) + } + clients.forEach((client) => { + if (client.socket) { + client.socket.emit(evt, data) + } + }) + } + + // Emits event to all admin user clients + adminEmitter(evt, data) { + for (const socketId in this.clients) { + if (this.clients[socketId].user && this.clients[socketId].user.isAdminOrUp) { + this.clients[socketId].socket.emit(evt, data) + } + } + } + + initialize(Server) { + this.Server = Server + + this.io = new SocketIO.Server(this.Server.server, { + cors: { + origin: '*', + methods: ["GET", "POST"] + } + }) + this.io.on('connection', (socket) => { + this.clients[socket.id] = { + id: socket.id, + socket, + connected_at: Date.now() + } + socket.sheepClient = this.clients[socket.id] + + Logger.info('[Server] Socket Connected', socket.id) + + // Required for associating a User with a socket + socket.on('auth', (token) => this.authenticateSocket(socket, token)) + + // Scanning + socket.on('cancel_scan', this.cancelScan.bind(this)) + + // Logs + socket.on('set_log_listener', (level) => Logger.addSocketListener(socket, level)) + socket.on('remove_log_listener', () => Logger.removeSocketListener(socket.id)) + socket.on('fetch_daily_logs', () => this.Server.logManager.socketRequestDailyLogs(socket)) + + // Sent automatically from socket.io clients + socket.on('disconnect', (reason) => { + Logger.removeSocketListener(socket.id) + + const _client = this.clients[socket.id] + if (!_client) { + Logger.warn(`[Server] Socket ${socket.id} disconnect, no client (Reason: ${reason})`) + } else if (!_client.user) { + Logger.info(`[Server] Unauth socket ${socket.id} disconnected (Reason: ${reason})`) + delete this.clients[socket.id] + } else { + Logger.debug('[Server] User Offline ' + _client.user.username) + this.adminEmitter('user_offline', _client.user.toJSONForPublic(this.Server.playbackSessionManager.sessions, this.Server.db.libraryItems)) + + const disconnectTime = Date.now() - _client.connected_at + Logger.info(`[Server] Socket ${socket.id} disconnected from client "${_client.user.username}" after ${disconnectTime}ms (Reason: ${reason})`) + delete this.clients[socket.id] + } + }) + + // + // Events for testing + // + socket.on('message_all_users', (payload) => { + // admin user can send a message to all authenticated users + // displays on the web app as a toast + const client = this.clients[socket.id] || {} + if (client.user && client.user.isAdminOrUp) { + this.emitter('admin_message', payload.message || '') + } else { + Logger.error(`[Server] Non-admin user sent the message_all_users event`) + } + }) + socket.on('ping', () => { + const client = this.clients[socket.id] || {} + const user = client.user || {} + Logger.debug(`[Server] Received ping from socket ${user.username || 'No User'}`) + socket.emit('pong') + }) + }) + } + + // When setting up a socket connection the user needs to be associated with a socket id + // for this the client will send a 'auth' event that includes the users API token + async authenticateSocket(socket, token) { + const user = await this.Server.auth.authenticateUser(token) + if (!user) { + Logger.error('Cannot validate socket - invalid token') + return socket.emit('invalid_token') + } + const client = this.clients[socket.id] + + if (client.user !== undefined) { + Logger.debug(`[Server] Authenticating socket client already has user`, client.user.username) + } + + client.user = user + + if (!client.user.toJSONForBrowser) { + Logger.error('Invalid user...', client.user) + return + } + + Logger.debug(`[Server] User Online ${client.user.username}`) + + this.adminEmitter('user_online', client.user.toJSONForPublic(this.Server.playbackSessionManager.sessions, this.Server.db.libraryItems)) + + // Update user lastSeen + user.lastSeen = Date.now() + await this.Server.db.updateEntity('user', user) + + const initialPayload = { + userId: client.user.id, + username: client.user.username, + librariesScanning: this.Server.scanner.librariesScanning + } + if (user.isAdminOrUp) { + initialPayload.usersOnline = this.getUsersOnline() + } + client.socket.emit('init', initialPayload) + } + + logout(socketId) { + // Strip user and client from client and client socket + if (socketId && this.clients[socketId]) { + const client = this.clients[socketId] + const clientSocket = client.socket + Logger.debug(`[Server] Found user client ${clientSocket.id}, Has user: ${!!client.user}, Socket has client: ${!!clientSocket.sheepClient}`) + + if (client.user) { + Logger.debug('[Server] User Offline ' + client.user.username) + this.adminEmitter('user_offline', client.user.toJSONForPublic(null, this.Server.db.libraryItems)) + } + + delete this.clients[socketId].user + if (clientSocket && clientSocket.sheepClient) delete this.clients[socketId].socket.sheepClient + } else if (socketId) { + Logger.warn(`[Server] No client for socket ${socketId}`) + } + } + + cancelScan(id) { + Logger.debug('[Server] Cancel scan', id) + this.Server.scanner.setCancelLibraryScan(id) + } +} +module.exports = new SocketAuthority() \ No newline at end of file diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js index 318538b9..96daefe8 100644 --- a/server/controllers/AuthorController.js +++ b/server/controllers/AuthorController.js @@ -1,4 +1,6 @@ const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') + const { reqSupportsWebp } = require('../utils/index') const { createNewSortInstance } = require('../libs/fastSort') @@ -93,18 +95,18 @@ class AuthorController { }) if (itemsWithAuthor.length) { await this.db.updateLibraryItems(itemsWithAuthor) - this.emitter('items_updated', itemsWithAuthor.map(li => li.toJSONExpanded())) + SocketAuthority.emitter('items_updated', itemsWithAuthor.map(li => li.toJSONExpanded())) } // Remove old author await this.db.removeEntity('author', req.author.id) - this.emitter('author_removed', req.author.toJSON()) + SocketAuthority.emitter('author_removed', req.author.toJSON()) // Send updated num books for merged author var numBooks = this.db.libraryItems.filter(li => { return li.media.metadata.hasAuthor && li.media.metadata.hasAuthor(existingAuthor.id) }).length - this.emitter('author_updated', existingAuthor.toJSONExpanded(numBooks)) + SocketAuthority.emitter('author_updated', existingAuthor.toJSONExpanded(numBooks)) res.json({ author: existingAuthor.toJSON(), @@ -121,7 +123,7 @@ class AuthorController { }) if (itemsWithAuthor.length) { await this.db.updateLibraryItems(itemsWithAuthor) - this.emitter('items_updated', itemsWithAuthor.map(li => li.toJSONExpanded())) + SocketAuthority.emitter('items_updated', itemsWithAuthor.map(li => li.toJSONExpanded())) } } @@ -129,7 +131,7 @@ class AuthorController { var numBooks = this.db.libraryItems.filter(li => { return li.media.metadata.hasAuthor && li.media.metadata.hasAuthor(req.author.id) }).length - this.emitter('author_updated', req.author.toJSONExpanded(numBooks)) + SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks)) } res.json({ @@ -190,7 +192,7 @@ class AuthorController { var numBooks = this.db.libraryItems.filter(li => { return li.media.metadata.hasAuthor && li.media.metadata.hasAuthor(req.author.id) }).length - this.emitter('author_updated', req.author.toJSONExpanded(numBooks)) + SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks)) } res.json({ diff --git a/server/controllers/BackupController.js b/server/controllers/BackupController.js index 79b07fd1..7ee482a9 100644 --- a/server/controllers/BackupController.js +++ b/server/controllers/BackupController.js @@ -3,32 +3,29 @@ const Logger = require('../Logger') class BackupController { constructor() { } - async create(req, res) { - if (!req.user.isAdminOrUp) { - Logger.error(`[BackupController] Non-admin user attempting to craete backup`, req.user) - return res.sendStatus(403) - } + getAll(req, res) { + res.json({ + backups: this.backupManager.backups.map(b => b.toJSON()) + }) + } + + create(req, res) { this.backupManager.requestCreateBackup(res) } async delete(req, res) { - if (!req.user.isAdminOrUp) { - Logger.error(`[BackupController] Non-admin user attempting to delete backup`, req.user) - return res.sendStatus(403) - } var backup = this.backupManager.backups.find(b => b.id === req.params.id) if (!backup) { return res.sendStatus(404) } await this.backupManager.removeBackup(backup) - res.json(this.backupManager.backups.map(b => b.toJSON())) + + res.json({ + backups: this.backupManager.backups.map(b => b.toJSON()) + }) } async upload(req, res) { - if (!req.user.isAdminOrUp) { - Logger.error(`[BackupController] Non-admin user attempting to upload backup`, req.user) - return res.sendStatus(403) - } if (!req.files.file) { Logger.error('[BackupController] Upload backup invalid') return res.sendStatus(500) @@ -37,10 +34,6 @@ class BackupController { } async apply(req, res) { - if (!req.user.isAdminOrUp) { - Logger.error(`[BackupController] Non-admin user attempting to apply backup`, req.user) - return res.sendStatus(403) - } var backup = this.backupManager.backups.find(b => b.id === req.params.id) if (!backup) { return res.sendStatus(404) @@ -48,5 +41,13 @@ class BackupController { await this.backupManager.requestApplyBackup(backup) res.sendStatus(200) } + + middleware(req, res, next) { + if (!req.user.isAdminOrUp) { + Logger.error(`[BackupController] Non-admin user attempting to access backups`, req.user) + return res.sendStatus(403) + } + next() + } } module.exports = new BackupController() \ No newline at end of file diff --git a/server/controllers/CollectionController.js b/server/controllers/CollectionController.js index 8508eb73..9234abee 100644 --- a/server/controllers/CollectionController.js +++ b/server/controllers/CollectionController.js @@ -1,4 +1,6 @@ const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') + const Collection = require('../objects/Collection') class CollectionController { @@ -13,7 +15,7 @@ class CollectionController { } var jsonExpanded = newCollection.toJSONExpanded(this.db.libraryItems) await this.db.insertEntity('collection', newCollection) - this.emitter('collection_added', jsonExpanded) + SocketAuthority.emitter('collection_added', jsonExpanded) res.json(jsonExpanded) } @@ -32,7 +34,7 @@ class CollectionController { var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems) if (wasUpdated) { await this.db.updateEntity('collection', collection) - this.emitter('collection_updated', jsonExpanded) + SocketAuthority.emitter('collection_updated', jsonExpanded) } res.json(jsonExpanded) } @@ -41,7 +43,7 @@ class CollectionController { const collection = req.collection var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems) await this.db.removeEntity('collection', collection.id) - this.emitter('collection_removed', jsonExpanded) + SocketAuthority.emitter('collection_removed', jsonExpanded) res.sendStatus(200) } @@ -60,7 +62,7 @@ class CollectionController { collection.addBook(req.body.id) var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems) await this.db.updateEntity('collection', collection) - this.emitter('collection_updated', jsonExpanded) + SocketAuthority.emitter('collection_updated', jsonExpanded) res.json(jsonExpanded) } @@ -71,7 +73,7 @@ class CollectionController { collection.removeBook(req.params.bookId) var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems) await this.db.updateEntity('collection', collection) - this.emitter('collection_updated', jsonExpanded) + SocketAuthority.emitter('collection_updated', jsonExpanded) } res.json(collection.toJSONExpanded(this.db.libraryItems)) } @@ -92,7 +94,7 @@ class CollectionController { } if (hasUpdated) { await this.db.updateEntity('collection', collection) - this.emitter('collection_updated', collection.toJSONExpanded(this.db.libraryItems)) + SocketAuthority.emitter('collection_updated', collection.toJSONExpanded(this.db.libraryItems)) } res.json(collection.toJSONExpanded(this.db.libraryItems)) } @@ -113,7 +115,7 @@ class CollectionController { } if (hasUpdated) { await this.db.updateEntity('collection', collection) - this.emitter('collection_updated', collection.toJSONExpanded(this.db.libraryItems)) + SocketAuthority.emitter('collection_updated', collection.toJSONExpanded(this.db.libraryItems)) } res.json(collection.toJSONExpanded(this.db.libraryItems)) } diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index dbc29ba3..b724ed0c 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -2,6 +2,7 @@ const Path = require('path') const fs = require('../libs/fsExtra') const filePerms = require('../utils/filePerms') const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') const Library = require('../objects/Library') const libraryHelpers = require('../utils/libraryHelpers') const { sort, createNewSortInstance } = require('../libs/fastSort') @@ -43,7 +44,7 @@ class LibraryController { library.setData(newLibraryPayload) await this.db.insertEntity('library', library) // TODO: Only emit to users that have access - this.emitter('library_added', library.toJSON()) + SocketAuthority.emitter('library_added', library.toJSON()) // Add library watcher this.watcher.addLibrary(library) @@ -120,7 +121,7 @@ class LibraryController { } } await this.db.updateEntity('library', library) - this.emitter('library_updated', library.toJSON()) + SocketAuthority.emitter('library_updated', library.toJSON()) } return res.json(library.toJSON()) } @@ -147,7 +148,7 @@ class LibraryController { var libraryJson = library.toJSON() await this.db.removeEntity('library', library.id) - this.emitter('library_removed', libraryJson) + SocketAuthority.emitter('library_removed', libraryJson) return res.json(libraryJson) } diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 20df9dad..8f573312 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -1,4 +1,6 @@ const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') + const { reqSupportsWebp, isNullOrNaN } = require('../utils/index') const { ScanResult } = require('../utils/constants') @@ -53,7 +55,7 @@ class LibraryItemController { if (hasUpdates) { Logger.debug(`[LibraryItemController] Updated now saving`) await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) } res.json(libraryItem.toJSON()) } @@ -97,7 +99,7 @@ class LibraryItemController { Logger.debug(`[LibraryItemController] Updated library item media ${libraryItem.media.metadata.title}`) await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) } res.json({ updated: hasUpdates, @@ -132,7 +134,7 @@ class LibraryItemController { } await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) res.json({ success: true, cover: result.cover @@ -152,7 +154,7 @@ class LibraryItemController { } if (validationResult.updated) { await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) } res.json({ success: true, @@ -168,7 +170,7 @@ class LibraryItemController { libraryItem.updateMediaCover('') await this.cacheManager.purgeCoverCache(libraryItem.id) await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) } res.sendStatus(200) @@ -228,7 +230,7 @@ class LibraryItemController { } libraryItem.media.updateAudioTracks(orderedFileData) await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) res.json(libraryItem.toJSON()) } @@ -284,7 +286,7 @@ class LibraryItemController { if (hasUpdates) { Logger.debug(`[LibraryItemController] Updated library item media ${libraryItem.media.metadata.title}`) await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) itemsUpdated++ } } @@ -342,7 +344,7 @@ class LibraryItemController { updates: itemsUpdated, unmatched: itemsUnmatched } - this.clientEmitter(req.user.id, 'batch_quickmatch_complete', result) + SocketAuthority.clientEmitter(req.user.id, 'batch_quickmatch_complete', result) } // DELETE: api/items/all @@ -410,7 +412,7 @@ class LibraryItemController { const wasUpdated = req.libraryItem.media.updateChapters(chapters) if (wasUpdated) { await this.db.updateLibraryItem(req.libraryItem) - this.emitter('item_updated', req.libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', req.libraryItem.toJSONExpanded()) } res.json({ diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js index 8a4848ee..3029a170 100644 --- a/server/controllers/MeController.js +++ b/server/controllers/MeController.js @@ -1,4 +1,5 @@ const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') const { sort } = require('../libs/fastSort') const { isObject, toNumber } = require('../utils/index') @@ -48,7 +49,7 @@ class MeController { return res.sendStatus(200) } await this.db.updateEntity('user', req.user) - this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) res.sendStatus(200) } @@ -62,7 +63,7 @@ class MeController { var wasUpdated = req.user.createUpdateMediaProgress(libraryItem, req.body) if (wasUpdated) { await this.db.updateEntity('user', req.user) - this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) } res.sendStatus(200) } @@ -82,7 +83,7 @@ class MeController { var wasUpdated = req.user.createUpdateMediaProgress(libraryItem, req.body, episodeId) if (wasUpdated) { await this.db.updateEntity('user', req.user) - this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) } res.sendStatus(200) } @@ -91,7 +92,7 @@ class MeController { async batchUpdateMediaProgress(req, res) { var itemProgressPayloads = req.body if (!itemProgressPayloads || !itemProgressPayloads.length) { - return res.sendStatus(500) + return res.status(400).send('Missing request payload') } var shouldUpdate = false @@ -107,7 +108,7 @@ class MeController { if (shouldUpdate) { await this.db.updateEntity('user', req.user) - this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) } res.sendStatus(200) @@ -120,7 +121,7 @@ class MeController { const { time, title } = req.body var bookmark = req.user.createBookmark(libraryItem.id, time, title) await this.db.updateEntity('user', req.user) - this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) res.json(bookmark) } @@ -136,7 +137,7 @@ class MeController { var bookmark = req.user.updateBookmark(libraryItem.id, time, title) if (!bookmark) return res.sendStatus(500) await this.db.updateEntity('user', req.user) - this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) res.json(bookmark) } @@ -153,7 +154,7 @@ class MeController { } req.user.removeBookmark(libraryItem.id, time) await this.db.updateEntity('user', req.user) - this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) res.sendStatus(200) } @@ -233,7 +234,7 @@ class MeController { Logger.debug(`[MeController] syncLocalMediaProgress server updates = ${numServerProgressUpdates}, local updates = ${updatedLocalMediaProgress.length}`) if (numServerProgressUpdates > 0) { await this.db.updateEntity('user', req.user) - this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) } res.json({ @@ -288,7 +289,7 @@ class MeController { const hasUpdated = req.user.addSeriesToHideFromContinueListening(req.params.id) if (hasUpdated) { await this.db.updateEntity('user', req.user) - this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) } res.json(req.user.toJSONForBrowser()) } @@ -304,7 +305,7 @@ class MeController { const hasUpdated = req.user.removeSeriesFromHideFromContinueListening(req.params.id) if (hasUpdated) { await this.db.updateEntity('user', req.user) - this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) } res.json(req.user.toJSONForBrowser()) } @@ -314,7 +315,7 @@ class MeController { const hasUpdated = req.user.removeProgressFromContinueListening(req.params.id) if (hasUpdated) { await this.db.updateEntity('user', req.user) - this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser()) } res.json(req.user.toJSONForBrowser()) } diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js index 5c0b5e1e..2c5c7b48 100644 --- a/server/controllers/PodcastController.js +++ b/server/controllers/PodcastController.js @@ -1,11 +1,14 @@ -const axios = require('axios') -const fs = require('../libs/fsExtra') const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') + +const fs = require('../libs/fsExtra') + const { getPodcastFeed, findMatchingEpisodes } = require('../utils/podcastUtils') -const LibraryItem = require('../objects/LibraryItem') const { getFileTimestampsWithIno } = require('../utils/fileUtils') const filePerms = require('../utils/filePerms') +const LibraryItem = require('../objects/LibraryItem') + class PodcastController { async create(req, res) { @@ -75,7 +78,7 @@ class PodcastController { } await this.db.insertLibraryItem(libraryItem) - this.emitter('item_added', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_added', libraryItem.toJSONExpanded()) res.json(libraryItem.toJSONExpanded()) @@ -194,7 +197,7 @@ class PodcastController { var wasUpdated = libraryItem.media.updateEpisode(episodeId, req.body) if (wasUpdated) { await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) } res.json(libraryItem.toJSONExpanded()) @@ -229,7 +232,7 @@ class PodcastController { } await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) res.json(libraryItem.toJSON()) } diff --git a/server/controllers/SeriesController.js b/server/controllers/SeriesController.js index 9f11ac51..678f30e0 100644 --- a/server/controllers/SeriesController.js +++ b/server/controllers/SeriesController.js @@ -1,4 +1,5 @@ const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') class SeriesController { constructor() { } @@ -38,7 +39,7 @@ class SeriesController { const hasUpdated = req.series.update(req.body) if (hasUpdated) { await this.db.updateEntity('series', req.series) - this.emitter('series_updated', req.series) + SocketAuthority.emitter('series_updated', req.series) } res.json(req.series) } diff --git a/server/controllers/UserController.js b/server/controllers/UserController.js index 2c2caf21..2d460a9c 100644 --- a/server/controllers/UserController.js +++ b/server/controllers/UserController.js @@ -1,4 +1,6 @@ const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') + const User = require('../objects/user/User') const { getId, toNumber } = require('../utils/index') @@ -44,7 +46,7 @@ class UserController { var newUser = new User(account) var success = await this.db.insertEntity('user', newUser) if (success) { - this.clientEmitter(req.user.id, 'user_added', newUser) + SocketAuthority.clientEmitter(req.user.id, 'user_added', newUser) res.json({ user: newUser.toJSONForBrowser() }) @@ -85,7 +87,7 @@ class UserController { Logger.info(`[UserController] User ${user.username} was generated a new api token`) } await this.db.updateEntity('user', user) - this.clientEmitter(req.user.id, 'user_updated', user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', user.toJSONForBrowser()) } res.json({ @@ -109,7 +111,7 @@ class UserController { var userJson = user.toJSONForBrowser() await this.db.removeEntity('user', user.id) - this.clientEmitter(req.user.id, 'user_removed', userJson) + SocketAuthority.clientEmitter(req.user.id, 'user_removed', userJson) res.json({ success: true }) @@ -170,7 +172,7 @@ class UserController { if (progressPurged) { Logger.info(`[UserController] Purged ${progressPurged} media progress for user ${user.username}`) await this.db.updateEntity('user', user) - this.clientEmitter(req.user.id, 'user_updated', user.toJSONForBrowser()) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', user.toJSONForBrowser()) } res.json(this.userJsonWithItemProgressDetails(user, !req.user.isRoot)) @@ -181,10 +183,9 @@ class UserController { if (!req.user.isAdminOrUp) { return res.sendStatus(403) } - const usersOnline = this.getUsersOnline() res.json({ - usersOnline, + usersOnline: SocketAuthority.getUsersOnline(), openSessions: this.playbackSessionManager.sessions }) } diff --git a/server/managers/AbMergeManager.js b/server/managers/AbMergeManager.js index 6fbce262..d5c33970 100644 --- a/server/managers/AbMergeManager.js +++ b/server/managers/AbMergeManager.js @@ -10,10 +10,9 @@ const { writeConcatFile } = require('../utils/ffmpegHelpers') const toneHelpers = require('../utils/toneHelpers') class AbMergeManager { - constructor(db, taskManager, clientEmitter) { + constructor(db, taskManager) { this.db = db this.taskManager = taskManager - this.clientEmitter = clientEmitter this.itemsCacheDir = Path.join(global.MetadataPath, 'cache/items') this.downloadDirPath = Path.join(global.MetadataPath, 'downloads') diff --git a/server/managers/AudioMetadataManager.js b/server/managers/AudioMetadataManager.js index 25a93583..d4d17211 100644 --- a/server/managers/AudioMetadataManager.js +++ b/server/managers/AudioMetadataManager.js @@ -1,18 +1,20 @@ const Path = require('path') -const fs = require('../libs/fsExtra') const workerThreads = require('worker_threads') + +const SocketAuthority = require('../SocketAuthority') const Logger = require('../Logger') + +const fs = require('../libs/fsExtra') + const filePerms = require('../utils/filePerms') const { secondsToTimestamp } = require('../utils/index') const { writeMetadataFile } = require('../utils/ffmpegHelpers') const toneHelpers = require('../utils/toneHelpers') class AudioMetadataMangaer { - constructor(db, taskManager, emitter, clientEmitter) { + constructor(db, taskManager) { this.db = db this.taskManager = taskManager - this.emitter = emitter - this.clientEmitter = clientEmitter } updateMetadataForItem(user, libraryItem, useTone, forceEmbedChapters) { @@ -40,7 +42,7 @@ class AudioMetadataMangaer { audioFiles: audioFiles.map(af => ({ index: af.index, ino: af.ino, filename: af.metadata.filename })) } - this.emitter('audio_metadata_started', itemAudioMetadataPayload) + SocketAuthority.emitter('audio_metadata_started', itemAudioMetadataPayload) // Write chapters file var toneJsonPath = null @@ -67,7 +69,7 @@ class AudioMetadataMangaer { itemAudioMetadataPayload.results = results itemAudioMetadataPayload.elapsed = elapsed itemAudioMetadataPayload.finishedAt = Date.now() - this.emitter('audio_metadata_finished', itemAudioMetadataPayload) + SocketAuthority.emitter('audio_metadata_finished', itemAudioMetadataPayload) } async updateAudioFileMetadataWithTone(libraryItemId, audioFile, toneJsonPath, itemCacheDir) { @@ -77,7 +79,7 @@ class AudioMetadataMangaer { ino: audioFile.ino, filename: audioFile.metadata.filename } - this.emitter('audiofile_metadata_started', resultPayload) + SocketAuthority.emitter('audiofile_metadata_started', resultPayload) // Backup audio file try { @@ -98,7 +100,7 @@ class AudioMetadataMangaer { Logger.info(`[AudioMetadataManager] Successfully tagged audio file "${audioFile.metadata.path}"`) } - this.emitter('audiofile_metadata_finished', resultPayload) + SocketAuthority.emitter('audiofile_metadata_finished', resultPayload) return resultPayload } @@ -115,7 +117,7 @@ class AudioMetadataMangaer { audioFiles: audioFiles.map(af => ({ index: af.index, ino: af.ino, filename: af.metadata.filename })) } - this.emitter('audio_metadata_started', itemAudioMetadataPayload) + SocketAuthority.emitter('audio_metadata_started', itemAudioMetadataPayload) var downloadsPath = Path.join(global.MetadataPath, 'downloads') var outputDir = Path.join(downloadsPath, libraryItem.id) @@ -143,7 +145,7 @@ class AudioMetadataMangaer { itemAudioMetadataPayload.results = results itemAudioMetadataPayload.elapsed = elapsed itemAudioMetadataPayload.finishedAt = Date.now() - this.emitter('audio_metadata_finished', itemAudioMetadataPayload) + SocketAuthority.emitter('audio_metadata_finished', itemAudioMetadataPayload) } updateAudioFileMetadataWithFfmpeg(libraryItemId, audioFile, outputDir, metadataFilePath, coverPath = '') { @@ -154,7 +156,7 @@ class AudioMetadataMangaer { ino: audioFile.ino, filename: audioFile.metadata.filename } - this.emitter('audiofile_metadata_started', resultPayload) + SocketAuthority.emitter('audiofile_metadata_started', resultPayload) Logger.debug(`[AudioFileMetadataManager] Starting audio file metadata encode for "${audioFile.metadata.filename}"`) @@ -229,19 +231,19 @@ class AudioMetadataMangaer { Logger.debug(`[AudioFileMetadataManager] Audio file replaced successfully "${inputPath}"`) resultPayload.success = true - this.emitter('audiofile_metadata_finished', resultPayload) + SocketAuthority.emitter('audiofile_metadata_finished', resultPayload) resolve(resultPayload) }).catch((error) => { Logger.error(`[AudioFileMetadataManager] Audio file failed to move "${inputPath}"`, error) resultPayload.success = false - this.emitter('audiofile_metadata_finished', resultPayload) + SocketAuthority.emitter('audiofile_metadata_finished', resultPayload) resolve(resultPayload) }) } else { Logger.debug(`[AudioFileMetadataManager] Metadata encode FAILED for "${audioFile.metadata.filename}"`) resultPayload.success = false - this.emitter('audiofile_metadata_finished', resultPayload) + SocketAuthority.emitter('audiofile_metadata_finished', resultPayload) resolve(resultPayload) } } else if (message.type === 'FFMPEG') { diff --git a/server/managers/BackupManager.js b/server/managers/BackupManager.js index 01f05b90..4e387fa8 100644 --- a/server/managers/BackupManager.js +++ b/server/managers/BackupManager.js @@ -1,4 +1,6 @@ const Path = require('path') +const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') const cron = require('../libs/nodeCron') const fs = require('../libs/fsExtra') @@ -8,18 +10,16 @@ const StreamZip = require('../libs/nodeStreamZip') // Utils const { getFileSize } = require('../utils/fileUtils') const filePerms = require('../utils/filePerms') -const Logger = require('../Logger') const Backup = require('../objects/Backup') class BackupManager { - constructor(db, emitter) { + constructor(db) { this.BackupPath = Path.join(global.MetadataPath, 'backups') this.ItemsMetadataPath = Path.join(global.MetadataPath, 'items') this.AuthorsMetadataPath = Path.join(global.MetadataPath, 'authors') this.db = db - this.emitter = emitter this.scheduleTask = null @@ -106,13 +106,20 @@ class BackupManager { this.backups.push(backup) } - return res.json(this.backups.map(b => b.toJSON())) + res.json({ + backups: this.backups.map(b => b.toJSON()) + }) } async requestCreateBackup(res) { var backupSuccess = await this.runBackup() - if (backupSuccess) res.json(this.backups.map(b => b.toJSON())) - else res.sendStatus(500) + if (backupSuccess) { + res.json({ + backups: this.backups.map(b => b.toJSON()) + }) + } else { + res.sendStatus(500) + } } async requestApplyBackup(backup) { @@ -123,7 +130,7 @@ class BackupManager { await zip.extract('metadata-authors/', this.AuthorsMetadataPath) } await this.db.reinit() - this.emitter('backup_applied') + SocketAuthority.emitter('backup_applied') } async loadBackups() { diff --git a/server/managers/NotificationManager.js b/server/managers/NotificationManager.js index cf7a6042..33d13ab5 100644 --- a/server/managers/NotificationManager.js +++ b/server/managers/NotificationManager.js @@ -1,11 +1,11 @@ const axios = require('axios') const Logger = require("../Logger") +const SocketAuthority = require('../SocketAuthority') const { notificationData } = require('../utils/notifications') class NotificationManager { - constructor(db, emitter) { + constructor(db) { this.db = db - this.emitter = emitter this.sendingNotification = false this.notificationQueue = [] @@ -58,7 +58,7 @@ class NotificationManager { } await this.db.updateEntity('settings', this.db.notificationSettings) - this.emitter('notifications_updated', this.db.notificationSettings) + SocketAuthority.emitter('notifications_updated', this.db.notificationSettings) this.notificationFinished() } diff --git a/server/managers/PlaybackSessionManager.js b/server/managers/PlaybackSessionManager.js index 38693c97..23f991ad 100644 --- a/server/managers/PlaybackSessionManager.js +++ b/server/managers/PlaybackSessionManager.js @@ -1,22 +1,24 @@ const Path = require('path') -const date = require('../libs/dateAndTime') const serverVersion = require('../../package.json').version -const { PlayMethod } = require('../utils/constants') -const PlaybackSession = require('../objects/PlaybackSession') -const DeviceInfo = require('../objects/DeviceInfo') -const Stream = require('../objects/Stream') const Logger = require('../Logger') -const fs = require('../libs/fsExtra') +const SocketAuthority = require('../SocketAuthority') +const date = require('../libs/dateAndTime') +const fs = require('../libs/fsExtra') const uaParserJs = require('../libs/uaParser') const requestIp = require('../libs/requestIp') +const { PlayMethod } = require('../utils/constants') + +const PlaybackSession = require('../objects/PlaybackSession') +const DeviceInfo = require('../objects/DeviceInfo') +const Stream = require('../objects/Stream') + + class PlaybackSessionManager { - constructor(db, emitter, clientEmitter) { + constructor(db) { this.db = db this.StreamsPath = Path.join(global.MetadataPath, 'streams') - this.emitter = emitter - this.clientEmitter = clientEmitter this.sessions = [] this.localSessionLock = {} @@ -98,7 +100,7 @@ class PlaybackSessionManager { if (wasUpdated) { await this.db.updateEntity('user', user) var itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId) - this.clientEmitter(user.id, 'user_item_progress_updated', { + SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', { id: itemProgress.id, data: itemProgress.toJSON() }) @@ -147,7 +149,7 @@ class PlaybackSessionManager { newPlaybackSession.playMethod = PlayMethod.DIRECTPLAY } else { Logger.debug(`[PlaybackSessionManager] "${user.username}" starting stream session for item "${libraryItem.id}"`) - var stream = new Stream(newPlaybackSession.id, this.StreamsPath, user, libraryItem, episodeId, userStartTime, this.clientEmitter.bind(this)) + var stream = new Stream(newPlaybackSession.id, this.StreamsPath, user, libraryItem, episodeId, userStartTime) await stream.generatePlaylist() stream.start() // Start transcode @@ -167,7 +169,7 @@ class PlaybackSessionManager { user.currentSessionId = newPlaybackSession.id this.sessions.push(newPlaybackSession) - this.emitter('user_stream_update', user.toJSONForPublic(this.sessions, this.db.libraryItems)) + SocketAuthority.adminEmitter('user_stream_update', user.toJSONForPublic(this.sessions, this.db.libraryItems)) return newPlaybackSession } @@ -193,7 +195,7 @@ class PlaybackSessionManager { await this.db.updateEntity('user', user) var itemProgress = user.getMediaProgress(session.libraryItemId, session.episodeId) - this.clientEmitter(user.id, 'user_item_progress_updated', { + SocketAuthority.clientEmitter(user.id, 'user_item_progress_updated', { id: itemProgress.id, data: itemProgress.toJSON() }) @@ -211,7 +213,7 @@ class PlaybackSessionManager { await this.saveSession(session) } Logger.debug(`[PlaybackSessionManager] closeSession "${session.id}"`) - this.emitter('user_stream_update', user.toJSONForPublic(this.sessions, this.db.libraryItems)) + SocketAuthority.adminEmitter('user_stream_update', user.toJSONForPublic(this.sessions, this.db.libraryItems)) return this.removeSession(session.id) } diff --git a/server/managers/PodcastManager.js b/server/managers/PodcastManager.js index 59bc7ea4..74751d45 100644 --- a/server/managers/PodcastManager.js +++ b/server/managers/PodcastManager.js @@ -1,23 +1,24 @@ +const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') + const fs = require('../libs/fsExtra') const { getPodcastFeed } = require('../utils/podcastUtils') -const Logger = require('../Logger') - const { downloadFile, removeFile } = require('../utils/fileUtils') const filePerms = require('../utils/filePerms') const { levenshteinDistance } = require('../utils/index') const opmlParser = require('../utils/parsers/parseOPML') const prober = require('../utils/prober') + const LibraryFile = require('../objects/files/LibraryFile') const PodcastEpisodeDownload = require('../objects/PodcastEpisodeDownload') const PodcastEpisode = require('../objects/entities/PodcastEpisode') const AudioFile = require('../objects/files/AudioFile') class PodcastManager { - constructor(db, watcher, emitter, notificationManager) { + constructor(db, watcher, notificationManager) { this.db = db this.watcher = watcher - this.emitter = emitter this.notificationManager = notificationManager this.downloadQueue = [] @@ -63,11 +64,11 @@ class PodcastManager { async startPodcastEpisodeDownload(podcastEpisodeDownload) { if (this.currentDownload) { this.downloadQueue.push(podcastEpisodeDownload) - this.emitter('episode_download_queued', podcastEpisodeDownload.toJSONForClient()) + SocketAuthority.emitter('episode_download_queued', podcastEpisodeDownload.toJSONForClient()) return } - this.emitter('episode_download_started', podcastEpisodeDownload.toJSONForClient()) + SocketAuthority.emitter('episode_download_started', podcastEpisodeDownload.toJSONForClient()) this.currentDownload = podcastEpisodeDownload // Ignores all added files to this dir @@ -97,7 +98,7 @@ class PodcastManager { this.currentDownload.setFinished(false) } - this.emitter('episode_download_finished', this.currentDownload.toJSONForClient()) + SocketAuthority.emitter('episode_download_finished', this.currentDownload.toJSONForClient()) this.watcher.removeIgnoreDir(this.currentDownload.libraryItem.path) this.currentDownload = null @@ -141,7 +142,7 @@ class PodcastManager { libraryItem.updatedAt = Date.now() await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) if (this.currentDownload.isAutoDownload) { // Notifications only for auto downloaded episodes this.notificationManager.onPodcastEpisodeDownloaded(libraryItem, podcastEpisode) @@ -230,7 +231,7 @@ class PodcastManager { libraryItem.media.lastEpisodeCheck = Date.now() libraryItem.updatedAt = Date.now() await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) return libraryItem.media.autoDownloadEpisodes } @@ -269,7 +270,7 @@ class PodcastManager { libraryItem.media.lastEpisodeCheck = Date.now() libraryItem.updatedAt = Date.now() await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) return newEpisodes } diff --git a/server/managers/RssFeedManager.js b/server/managers/RssFeedManager.js index ce15bbea..bd90b96c 100644 --- a/server/managers/RssFeedManager.js +++ b/server/managers/RssFeedManager.js @@ -1,13 +1,15 @@ const Path = require('path') + +const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') + const fs = require('../libs/fsExtra') const Feed = require('../objects/Feed') -const Logger = require('../Logger') -// Not functional at the moment class RssFeedManager { - constructor(db, emitter) { + constructor(db) { this.db = db - this.emitter = emitter + this.feeds = {} } @@ -104,7 +106,7 @@ class RssFeedManager { Logger.debug(`[RssFeedManager] Opened RSS feed ${feed.feedUrl}`) await this.db.insertEntity('feed', feed) - this.emitter('rss_feed_open', { id: feed.id, entityType: feed.entityType, entityId: feed.entityId, feedUrl: feed.feedUrl }) + SocketAuthority.emitter('rss_feed_open', { id: feed.id, entityType: feed.entityType, entityId: feed.entityId, feedUrl: feed.feedUrl }) return feed } @@ -118,7 +120,7 @@ class RssFeedManager { if (!this.feeds[id]) return var feed = this.feeds[id] await this.db.removeEntity('feed', id) - this.emitter('rss_feed_closed', { id: feed.id, entityType: feed.entityType, entityId: feed.entityId, feedUrl: feed.feedUrl }) + SocketAuthority.emitter('rss_feed_closed', { id: feed.id, entityType: feed.entityType, entityId: feed.entityId, feedUrl: feed.feedUrl }) delete this.feeds[id] Logger.info(`[RssFeedManager] Closed RSS feed "${feed.feedUrl}"`) } diff --git a/server/managers/TaskManager.js b/server/managers/TaskManager.js index d962d0c8..38e8b580 100644 --- a/server/managers/TaskManager.js +++ b/server/managers/TaskManager.js @@ -1,19 +1,19 @@ -class TaskManager { - constructor(emitter) { - this.emitter = emitter +const SocketAuthority = require('../SocketAuthority') +class TaskManager { + constructor() { this.tasks = [] } addTask(task) { this.tasks.push(task) - this.emitter('task_started', task.toJSON()) + SocketAuthority.emitter('task_started', task.toJSON()) } taskFinished(task) { if (this.tasks.some(t => t.id === task.id)) { this.tasks = this.tasks.filter(t => t.id !== task.id) - this.emitter('task_finished', task.toJSON()) + SocketAuthority.emitter('task_finished', task.toJSON()) } } } diff --git a/server/objects/FeedEpisode.js b/server/objects/FeedEpisode.js index 28a60e78..ed3a7e21 100644 --- a/server/objects/FeedEpisode.js +++ b/server/objects/FeedEpisode.js @@ -92,11 +92,15 @@ class FeedEpisode { const media = libraryItem.media const mediaMetadata = media.metadata - var title = audioTrack.title - if (libraryItem.media.chapters.length) { - // If audio track start and chapter start are within 1 seconds of eachother then use the chapter title - var matchingChapter = libraryItem.media.chapters.find(ch => Math.abs(ch.start - audioTrack.startOffset) < 1) - if (matchingChapter && matchingChapter.title) title = matchingChapter.title + let title = audioTrack.title + if (libraryItem.media.tracks.length == 1) { // If audiobook is a single file, use book title instead of chapter/file title + title = libraryItem.media.metadata.title + } else { + if (libraryItem.media.chapters.length) { + // If audio track start and chapter start are within 1 seconds of eachother then use the chapter title + var matchingChapter = libraryItem.media.chapters.find(ch => Math.abs(ch.start - audioTrack.startOffset) < 1) + if (matchingChapter && matchingChapter.title) title = matchingChapter.title + } } this.id = String(audioTrack.index) diff --git a/server/objects/Stream.js b/server/objects/Stream.js index 19ea46e0..90403ea3 100644 --- a/server/objects/Stream.js +++ b/server/objects/Stream.js @@ -1,8 +1,12 @@ -const Ffmpeg = require('../libs/fluentFfmpeg') + const EventEmitter = require('events') const Path = require('path') -const fs = require('../libs/fsExtra') const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') + +const fs = require('../libs/fsExtra') +const Ffmpeg = require('../libs/fluentFfmpeg') + const { secondsToTimestamp } = require('../utils/index') const { writeConcatFile } = require('../utils/ffmpegHelpers') const { AudioMimeType } = require('../utils/constants') @@ -10,14 +14,13 @@ const hlsPlaylistGenerator = require('../utils/hlsPlaylistGenerator') const AudioTrack = require('./files/AudioTrack') class Stream extends EventEmitter { - constructor(sessionId, streamPath, user, libraryItem, episodeId, startTime, clientEmitter, transcodeOptions = {}) { + constructor(sessionId, streamPath, user, libraryItem, episodeId, startTime, transcodeOptions = {}) { super() this.id = sessionId this.user = user this.libraryItem = libraryItem this.episodeId = episodeId - this.clientEmitter = clientEmitter this.transcodeOptions = transcodeOptions @@ -408,7 +411,7 @@ class Stream extends EventEmitter { } clientEmit(evtName, data) { - if (this.clientEmitter) this.clientEmitter(this.user.id, evtName, data) + SocketAuthority.clientEmitter(this.user.id, evtName, data) } getAudioTrack() { diff --git a/server/objects/user/MediaProgress.js b/server/objects/user/MediaProgress.js index 70b2d678..3c4b4ca3 100644 --- a/server/objects/user/MediaProgress.js +++ b/server/objects/user/MediaProgress.js @@ -63,12 +63,12 @@ class MediaProgress { this.isFinished = !!progress.isFinished || this.progress == 1 this.hideFromContinueListening = !!progress.hideFromContinueListening this.lastUpdate = Date.now() - this.startedAt = Date.now() this.finishedAt = null if (this.isFinished) { - this.finishedAt = Date.now() + this.finishedAt = progress.finishedAt || Date.now() this.progress = 1 } + this.startedAt = progress.startedAt || this.finishedAt || Date.now() } update(payload) { @@ -95,7 +95,7 @@ class MediaProgress { // If time remaining is less than 5 seconds then mark as finished if ((this.progress >= 1 || (this.duration && !isNaN(timeRemaining) && timeRemaining < 5))) { this.isFinished = true - this.finishedAt = Date.now() + this.finishedAt = payload.finishedAt || Date.now() this.progress = 1 } else if (this.progress < 1 && this.isFinished) { this.isFinished = false @@ -103,7 +103,7 @@ class MediaProgress { } if (!this.startedAt) { - this.startedAt = Date.now() + this.startedAt = this.finishedAt || Date.now() } if (hasUpdates) { if (payload.hideFromContinueListening === undefined) { diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index d296cf50..febcb4f3 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -1,8 +1,11 @@ const express = require('express') const Path = require('path') + +const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') + const fs = require('../libs/fsExtra') const date = require('../libs/dateAndTime') -const Logger = require('../Logger') const LibraryController = require('../controllers/LibraryController') const UserController = require('../controllers/UserController') @@ -29,25 +32,22 @@ const Author = require('../objects/entities/Author') const Series = require('../objects/entities/Series') class ApiRouter { - constructor(db, auth, scanner, playbackSessionManager, abMergeManager, coverManager, backupManager, watcher, cacheManager, podcastManager, audioMetadataManager, rssFeedManager, cronManager, notificationManager, taskManager, getUsersOnline, emitter, clientEmitter) { - this.db = db - this.auth = auth - this.scanner = scanner - this.playbackSessionManager = playbackSessionManager - this.abMergeManager = abMergeManager - this.backupManager = backupManager - this.coverManager = coverManager - this.watcher = watcher - this.cacheManager = cacheManager - this.podcastManager = podcastManager - this.audioMetadataManager = audioMetadataManager - this.rssFeedManager = rssFeedManager - this.cronManager = cronManager - this.notificationManager = notificationManager - this.taskManager = taskManager - this.getUsersOnline = getUsersOnline - this.emitter = emitter - this.clientEmitter = clientEmitter + constructor(Server) { + this.db = Server.db + this.auth = Server.auth + this.scanner = Server.scanner + this.playbackSessionManager = Server.playbackSessionManager + this.abMergeManager = Server.abMergeManager + this.backupManager = Server.backupManager + this.coverManager = Server.coverManager + this.watcher = Server.watcher + this.cacheManager = Server.cacheManager + this.podcastManager = Server.podcastManager + this.audioMetadataManager = Server.audioMetadataManager + this.rssFeedManager = Server.rssFeedManager + this.cronManager = Server.cronManager + this.notificationManager = Server.notificationManager + this.taskManager = Server.taskManager this.bookFinder = new BookFinder() this.authorFinder = new AuthorFinder() @@ -163,10 +163,11 @@ class ApiRouter { // // Backup Routes // - this.router.post('/backups', BackupController.create.bind(this)) - this.router.delete('/backups/:id', BackupController.delete.bind(this)) - this.router.get('/backups/:id/apply', BackupController.apply.bind(this)) - this.router.post('/backups/upload', BackupController.upload.bind(this)) + this.router.get('/backups', BackupController.middleware.bind(this), BackupController.getAll.bind(this)) + this.router.post('/backups', BackupController.middleware.bind(this), BackupController.create.bind(this)) + this.router.delete('/backups/:id', BackupController.middleware.bind(this), BackupController.delete.bind(this)) + this.router.get('/backups/:id/apply', BackupController.middleware.bind(this), BackupController.apply.bind(this)) + this.router.post('/backups/upload', BackupController.middleware.bind(this), BackupController.upload.bind(this)) // // File System Routes @@ -261,13 +262,13 @@ class ApiRouter { async getDirectories(dir, relpath, excludedDirs, level = 0) { try { - var paths = await fs.readdir(dir) + const paths = await fs.readdir(dir) - var dirs = await Promise.all(paths.map(async dirname => { - var fullPath = Path.join(dir, dirname) - var path = Path.join(relpath, dirname) + let dirs = await Promise.all(paths.map(async dirname => { + const fullPath = Path.join(dir, dirname) + const path = Path.join(relpath, dirname) - var isDir = (await fs.lstat(fullPath)).isDirectory() + const isDir = (await fs.lstat(fullPath)).isDirectory() if (isDir && !excludedDirs.includes(path) && dirname !== 'node_modules') { return { path, @@ -292,13 +293,13 @@ class ApiRouter { // Helper Methods // userJsonWithItemProgressDetails(user, hideRootToken = false) { - var json = user.toJSONForBrowser() + const json = user.toJSONForBrowser() if (json.type === 'root' && hideRootToken) { json.token = '' } json.mediaProgress = json.mediaProgress.map(lip => { - var libraryItem = this.db.libraryItems.find(li => li.id === lip.libraryItemId) + const libraryItem = this.db.libraryItems.find(li => li.id === lip.libraryItemId) if (!libraryItem) { Logger.warn('[ApiRouter] Library item not found for users progress ' + lip.libraryItemId) lip.media = null @@ -325,34 +326,21 @@ class ApiRouter { async handleDeleteLibraryItem(libraryItem) { // Remove libraryItem from users for (let i = 0; i < this.db.users.length; i++) { - var user = this.db.users[i] - var madeUpdates = user.removeMediaProgressForLibraryItem(libraryItem.id) - if (madeUpdates) { + const user = this.db.users[i] + if (user.removeMediaProgressForLibraryItem(libraryItem.id)) { await this.db.updateEntity('user', user) } } - // remove any streams open for this audiobook - // TODO: Change to PlaybackSessionManager to remove open sessions for user - // var streams = this.streamManager.streams.filter(stream => stream.audiobookId === libraryItem.id) - // for (let i = 0; i < streams.length; i++) { - // var stream = streams[i] - // var client = stream.client - // await stream.close() - // if (client && client.user) { - // client.user.stream = null - // client.stream = null - // this.db.updateUserStream(client.user.id, null) - // } - // } + // TODO: Remove open sessions for library item // remove book from collections - var collectionsWithBook = this.db.collections.filter(c => c.books.includes(libraryItem.id)) + const collectionsWithBook = this.db.collections.filter(c => c.books.includes(libraryItem.id)) for (let i = 0; i < collectionsWithBook.length; i++) { - var collection = collectionsWithBook[i] + const collection = collectionsWithBook[i] collection.removeBook(libraryItem.id) await this.db.updateEntity('collection', collection) - this.clientEmitter(collection.userId, 'collection_updated', collection.toJSONExpanded(this.db.libraryItems)) + SocketAuthority.clientEmitter(collection.userId, 'collection_updated', collection.toJSONExpanded(this.db.libraryItems)) } // purge cover cache @@ -360,34 +348,32 @@ class ApiRouter { await this.cacheManager.purgeCoverCache(libraryItem.id) } - var json = libraryItem.toJSONExpanded() await this.db.removeLibraryItem(libraryItem.id) - this.emitter('item_removed', json) + SocketAuthority.emitter('item_removed', libraryItem.toJSONExpanded()) } async getUserListeningSessionsHelper(userId) { - var userSessions = await this.db.selectUserSessions(userId) + const userSessions = await this.db.selectUserSessions(userId) return userSessions.sort((a, b) => b.updatedAt - a.updatedAt) } async getAllSessionsWithUserData() { - var sessions = await this.db.getAllSessions() + const sessions = await this.db.getAllSessions() sessions.sort((a, b) => b.updatedAt - a.updatedAt) return sessions.map(se => { - var user = this.db.users.find(u => u.id === se.userId) - var _se = { + const user = this.db.users.find(u => u.id === se.userId) + return { ...se, user: user ? { id: user.id, username: user.username } : null } - return _se }) } async getUserListeningStatsHelpers(userId) { const today = date.format(new Date(), 'YYYY-MM-DD') - var listeningSessions = await this.getUserListeningSessionsHelper(userId) - var listeningStats = { + const listeningSessions = await this.getUserListeningSessionsHelper(userId) + const listeningStats = { totalTime: 0, items: {}, days: {}, @@ -396,7 +382,7 @@ class ApiRouter { recentSessions: listeningSessions.slice(0, 10) } listeningSessions.forEach((s) => { - var sessionTimeListening = s.timeListening + let sessionTimeListening = s.timeListening if (typeof sessionTimeListening == 'string') { sessionTimeListening = Number(sessionTimeListening) } @@ -431,15 +417,15 @@ class ApiRouter { async createAuthorsAndSeriesForItemUpdate(mediaPayload) { if (mediaPayload.metadata) { - var mediaMetadata = mediaPayload.metadata + const mediaMetadata = mediaPayload.metadata // Create new authors if in payload if (mediaMetadata.authors && mediaMetadata.authors.length) { // TODO: validate authors - var newAuthors = [] + const newAuthors = [] for (let i = 0; i < mediaMetadata.authors.length; i++) { if (mediaMetadata.authors[i].id.startsWith('new')) { - var author = this.db.authors.find(au => au.checkNameEquals(mediaMetadata.authors[i].name)) + let author = this.db.authors.find(au => au.checkNameEquals(mediaMetadata.authors[i].name)) if (!author) { author = new Author() author.setData(mediaMetadata.authors[i]) @@ -453,17 +439,17 @@ class ApiRouter { } if (newAuthors.length) { await this.db.insertEntities('author', newAuthors) - this.emitter('authors_added', newAuthors) + SocketAuthority.emitter('authors_added', newAuthors) } } // Create new series if in payload if (mediaMetadata.series && mediaMetadata.series.length) { // TODO: validate series - var newSeries = [] + const newSeries = [] for (let i = 0; i < mediaMetadata.series.length; i++) { if (mediaMetadata.series[i].id.startsWith('new')) { - var seriesItem = this.db.series.find(se => se.checkNameEquals(mediaMetadata.series[i].name)) + let seriesItem = this.db.series.find(se => se.checkNameEquals(mediaMetadata.series[i].name)) if (!seriesItem) { seriesItem = new Series() seriesItem.setData(mediaMetadata.series[i]) @@ -477,7 +463,7 @@ class ApiRouter { } if (newSeries.length) { await this.db.insertEntities('series', newSeries) - this.emitter('authors_added', newSeries) + SocketAuthority.emitter('authors_added', newSeries) } } } diff --git a/server/routers/HlsRouter.js b/server/routers/HlsRouter.js index 80a0913e..58896097 100644 --- a/server/routers/HlsRouter.js +++ b/server/routers/HlsRouter.js @@ -1,14 +1,17 @@ const express = require('express') const Path = require('path') -const fs = require('../libs/fsExtra') + const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') + +const fs = require('../libs/fsExtra') + class HlsRouter { - constructor(db, auth, playbackSessionManager, emitter) { + constructor(db, auth, playbackSessionManager) { this.db = db this.auth = auth this.playbackSessionManager = playbackSessionManager - this.emitter = emitter this.router = express() this.init() @@ -49,7 +52,7 @@ class HlsRouter { if (startTimeForReset) { // HLS.js will restart the stream at the new time Logger.info(`[HlsRouter] Resetting Stream - notify client @${startTimeForReset}s`) - this.emitter('stream_reset', { + SocketAuthority.emitter('stream_reset', { startTime: startTimeForReset, streamId: stream.id }) diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index 16d1355a..68dfdd8f 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -1,8 +1,9 @@ const fs = require('../libs/fsExtra') const Path = require('path') +const Logger = require('../Logger') +const SocketAuthority = require('../SocketAuthority') // Utils -const Logger = require('../Logger') const { groupFilesIntoLibraryItemPaths, getLibraryItemFileData, scanFolder } = require('../utils/scandir') const { comparePaths } = require('../utils/index') const { getIno } = require('../utils/fileUtils') @@ -20,12 +21,11 @@ const Author = require('../objects/entities/Author') const Series = require('../objects/entities/Series') class Scanner { - constructor(db, coverManager, emitter) { + constructor(db, coverManager) { this.ScanLogPath = Path.posix.join(global.MetadataPath, 'logs', 'scans') this.db = db this.coverManager = coverManager - this.emitter = emitter this.cancelLibraryScan = {} this.librariesScanning = [] @@ -113,7 +113,7 @@ class Scanner { } if (hasUpdated) { - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) await this.db.updateLibraryItem(libraryItem) return ScanResult.UPDATED } @@ -139,7 +139,7 @@ class Scanner { libraryScan.verbose = false this.librariesScanning.push(libraryScan.getScanEmitData) - this.emitter('scan_start', libraryScan.getScanEmitData) + SocketAuthority.emitter('scan_start', libraryScan.getScanEmitData) Logger.info(`[Scanner] Starting library scan ${libraryScan.id} for ${libraryScan.libraryName}`) @@ -158,11 +158,11 @@ class Scanner { if (canceled && !libraryScan.totalResults) { var emitData = libraryScan.getScanEmitData emitData.results = null - this.emitter('scan_complete', emitData) + SocketAuthority.emitter('scan_complete', emitData) return } - this.emitter('scan_complete', libraryScan.getScanEmitData) + SocketAuthority.emitter('scan_complete', libraryScan.getScanEmitData) if (libraryScan.totalResults) { libraryScan.saveLog(this.ScanLogPath) @@ -302,7 +302,7 @@ class Scanner { async updateLibraryItemChunk(itemsToUpdate) { await this.db.updateLibraryItems(itemsToUpdate) - this.emitter('items_updated', itemsToUpdate.map(li => li.toJSONExpanded())) + SocketAuthority.emitter('items_updated', itemsToUpdate.map(li => li.toJSONExpanded())) } async rescanLibraryItemDataChunk(itemDataToRescan, libraryScan) { @@ -320,7 +320,7 @@ class Scanner { if (itemsUpdated.length) { libraryScan.resultsUpdated += itemsUpdated.length await this.db.updateLibraryItems(itemsUpdated) - this.emitter('items_updated', itemsUpdated.map(li => li.toJSONExpanded())) + SocketAuthority.emitter('items_updated', itemsUpdated.map(li => li.toJSONExpanded())) } } @@ -337,7 +337,7 @@ class Scanner { libraryScan.resultsAdded += newLibraryItems.length await this.db.insertLibraryItems(newLibraryItems) - this.emitter('items_added', newLibraryItems.map(li => li.toJSONExpanded())) + SocketAuthority.emitter('items_added', newLibraryItems.map(li => li.toJSONExpanded())) } async rescanLibraryItem(libraryItemCheckData, libraryScan) { @@ -458,7 +458,7 @@ class Scanner { }) if (newAuthors.length) { await this.db.insertEntities('author', newAuthors) - this.emitter('authors_added', newAuthors.map(au => au.toJSON())) + SocketAuthority.emitter('authors_added', newAuthors.map(au => au.toJSON())) } } if (libraryItem.media.metadata.series.some(se => se.id.startsWith('new'))) { @@ -479,7 +479,7 @@ class Scanner { }) if (newSeries.length) { await this.db.insertEntities('series', newSeries) - this.emitter('series_added', newSeries.map(se => se.toJSON())) + SocketAuthority.emitter('series_added', newSeries.map(se => se.toJSON())) } } } @@ -602,7 +602,7 @@ class Scanner { Logger.info(`[Scanner] Scanning file update group and library item was deleted "${existingLibraryItem.media.metadata.title}" - marking as missing`) existingLibraryItem.setMissing() await this.db.updateLibraryItem(existingLibraryItem) - this.emitter('item_updated', existingLibraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', existingLibraryItem.toJSONExpanded()) itemGroupingResults[itemDir] = ScanResult.REMOVED continue; @@ -629,7 +629,7 @@ class Scanner { if (newLibraryItem) { await this.createNewAuthorsAndSeries(newLibraryItem) await this.db.insertLibraryItem(newLibraryItem) - this.emitter('item_added', newLibraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_added', newLibraryItem.toJSONExpanded()) } itemGroupingResults[itemDir] = newLibraryItem ? ScanResult.ADDED : ScanResult.NOTHING } @@ -747,7 +747,7 @@ class Scanner { } await this.db.updateLibraryItem(libraryItem) - this.emitter('item_updated', libraryItem.toJSONExpanded()) + SocketAuthority.emitter('item_updated', libraryItem.toJSONExpanded()) } return { @@ -846,7 +846,7 @@ class Scanner { author = new Author() author.setData({ name: authorName }) await this.db.insertEntity('author', author) - this.emitter('author_added', author) + SocketAuthority.emitter('author_added', author) } authorPayload.push(author.toJSONMinimal()) } @@ -864,7 +864,7 @@ class Scanner { seriesItem = new Series() seriesItem.setData({ name: seriesMatchItem.series }) await this.db.insertEntity('series', seriesItem) - this.emitter('series_added', seriesItem) + SocketAuthority.emitter('series_added', seriesItem) } seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.sequence)) } @@ -955,7 +955,7 @@ class Scanner { var libraryScan = new LibraryScan() libraryScan.setData(library, null, 'match') this.librariesScanning.push(libraryScan.getScanEmitData) - this.emitter('scan_start', libraryScan.getScanEmitData) + SocketAuthority.emitter('scan_start', libraryScan.getScanEmitData) Logger.info(`[Scanner] matchLibraryItems: Starting library match scan ${libraryScan.id} for ${libraryScan.libraryName}`) @@ -987,14 +987,14 @@ class Scanner { delete this.cancelLibraryScan[libraryScan.libraryId] var scanData = libraryScan.getScanEmitData scanData.results = false - this.emitter('scan_complete', scanData) + SocketAuthority.emitter('scan_complete', scanData) this.librariesScanning = this.librariesScanning.filter(ls => ls.id !== library.id) return } } this.librariesScanning = this.librariesScanning.filter(ls => ls.id !== library.id) - this.emitter('scan_complete', libraryScan.getScanEmitData) + SocketAuthority.emitter('scan_complete', libraryScan.getScanEmitData) } probeAudioFileWithTone(audioFile) {