From 0af6ad63c1d53600345a4ef05aa5e95c1f14530a Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 15 Mar 2022 19:28:54 -0500 Subject: [PATCH] New data model start of PlaybackSessionManager to replace StreamManager, remove podcast & ip npm package --- package-lock.json | 44 +---------------- package.json | 4 +- server/ApiController.js | 36 +++++++------- server/HlsController.js | 4 +- server/PlaybackSessionManager.js | 25 +++++++++- server/RssFeeds.js | 50 ++++++++++---------- server/Server.js | 40 +++++++++------- server/controllers/LibraryItemController.js | 8 +++- server/objects/{user => }/PlaybackSession.js | 8 ++-- server/{ => objects/legacy}/StreamManager.js | 4 +- server/utils/dbMigration.js | 2 +- 11 files changed, 109 insertions(+), 116 deletions(-) rename server/objects/{user => }/PlaybackSession.js (92%) rename server/{ => objects/legacy}/StreamManager.js (98%) diff --git a/package-lock.json b/package-lock.json index 895e1bbc..b19ee118 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "1.7.1", + "version": "1.7.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -764,11 +764,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1127,14 +1122,6 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, - "podcast": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/podcast/-/podcast-1.3.0.tgz", - "integrity": "sha512-L0UNP8SMdoihxgpdXCaXZEKZBBCGzld5PSy8QbQYsk83bdzq14cdW8flJduZjQNbB2If5frwVIC5VpMq9CHchA==", - "requires": { - "rss": "^1.2.2" - } - }, "printj": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", @@ -1305,30 +1292,6 @@ "atomically": "^1.7.0" } }, - "rss": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/rss/-/rss-1.2.2.tgz", - "integrity": "sha1-UKFpiHYTgTOnT5oF0r3I240nqSE=", - "requires": { - "mime-types": "2.1.13", - "xml": "1.0.1" - }, - "dependencies": { - "mime-db": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.25.0.tgz", - "integrity": "sha1-wY29fHOl2/b0SgJNwNFloeexw5I=" - }, - "mime-types": { - "version": "2.1.13", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.13.tgz", - "integrity": "sha1-4HqqnGxrmnyjASxpADrSWjnpKog=", - "requires": { - "mime-db": "~1.25.0" - } - } - } - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -1627,11 +1590,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" }, - "xml": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=" - }, "xml2js": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", diff --git a/package.json b/package.json index 198c5d58..8edece33 100644 --- a/package.json +++ b/package.json @@ -39,14 +39,12 @@ "fluent-ffmpeg": "^2.1.2", "fs-extra": "^10.0.0", "image-type": "^4.1.0", - "ip": "^1.1.5", "jsonwebtoken": "^8.5.1", "libgen": "^2.1.0", "njodb": "^0.4.29", "node-cron": "^3.0.0", "node-ffprobe": "^3.0.0", "node-stream-zip": "^1.15.0", - "podcast": "^1.3.0", "read-chunk": "^3.1.0", "recursive-readdir-async": "^1.1.8", "socket.io": "^4.1.3", @@ -55,4 +53,4 @@ "xml2js": "^0.4.23" }, "devDependencies": {} -} \ No newline at end of file +} diff --git a/server/ApiController.js b/server/ApiController.js index 3f0d7db5..51b06d12 100644 --- a/server/ApiController.js +++ b/server/ApiController.js @@ -26,11 +26,11 @@ const Series = require('./objects/entities/Series') const FileSystemController = require('./controllers/FileSystemController') class ApiController { - constructor(db, auth, scanner, streamManager, downloadManager, coverController, backupManager, watcher, cacheManager, emitter, clientEmitter) { + constructor(db, auth, scanner, playbackSessionManager, downloadManager, coverController, backupManager, watcher, cacheManager, emitter, clientEmitter) { this.db = db this.auth = auth this.scanner = scanner - this.streamManager = streamManager + this.playbackSessionManager = playbackSessionManager this.downloadManager = downloadManager this.backupManager = backupManager this.coverController = coverController @@ -83,13 +83,15 @@ class ApiController { this.router.post('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.uploadCover.bind(this)) this.router.patch('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.updateCover.bind(this)) this.router.delete('/items/:id/cover', LibraryItemController.middleware.bind(this), LibraryItemController.removeCover.bind(this)) - this.router.get('/items/:id/stream', LibraryItemController.middleware.bind(this), LibraryItemController.openStream.bind(this)) this.router.post('/items/:id/match', LibraryItemController.middleware.bind(this), LibraryItemController.match.bind(this)) this.router.patch('/items/:id/tracks', LibraryItemController.middleware.bind(this), LibraryItemController.updateTracks.bind(this)) + this.router.get('/items/:id/play', LibraryItemController.middleware.bind(this), LibraryItemController.startPlaybackSession.bind(this)) this.router.post('/items/batch/delete', LibraryItemController.batchDelete.bind(this)) this.router.post('/items/batch/update', LibraryItemController.batchUpdate.bind(this)) this.router.post('/items/batch/get', LibraryItemController.batchGet.bind(this)) + // Legacy + this.router.get('/items/:id/stream', LibraryItemController.middleware.bind(this), LibraryItemController.openStream.bind(this)) // // User Routes @@ -331,7 +333,8 @@ class ApiController { // Sync audiobook stream progress async syncStream(req, res) { Logger.debug(`[ApiController] syncStream for ${req.user.username} - ${req.body.streamId}`) - this.streamManager.streamSyncFromApi(req, res) + // this.streamManager.streamSyncFromApi(req, res) + res.sendStatus(500) } // Sync local downloaded audiobook progress @@ -381,17 +384,18 @@ class ApiController { } // remove any streams open for this audiobook - 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: 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) + // } + // } // remove book from collections var collectionsWithBook = this.db.collections.filter(c => c.books.includes(libraryItem.id)) @@ -472,7 +476,7 @@ class ApiController { async closeStream(req, res) { const streamId = req.params.id const userId = req.user.id - this.streamManager.closeStreamApiRequest(userId, streamId) + // this.streamManager.closeStreamApiRequest(userId, streamId) res.sendStatus(200) } diff --git a/server/HlsController.js b/server/HlsController.js index 35e80f52..eb18963d 100644 --- a/server/HlsController.js +++ b/server/HlsController.js @@ -4,10 +4,10 @@ const fs = require('fs-extra') const Logger = require('./Logger') class HlsController { - constructor(db, auth, streamManager, emitter) { + constructor(db, auth, playbackSessionManager, emitter) { this.db = db this.auth = auth - this.streamManager = streamManager + this.streamManager = playbackSessionManager this.emitter = emitter this.router = express() diff --git a/server/PlaybackSessionManager.js b/server/PlaybackSessionManager.js index 4f65db5b..70473b4c 100644 --- a/server/PlaybackSessionManager.js +++ b/server/PlaybackSessionManager.js @@ -1,8 +1,29 @@ - +const Path = require('path') +const PlaybackSession = require('./objects/PlaybackSession') class PlaybackSessionManager { - constructor() { + constructor(db, emitter, clientEmitter) { + this.db = db + this.StreamsPath = Path.join(global.MetadataPath, 'streams') + this.emitter = emitter + this.clientEmitter = clientEmitter + this.sessions = [] + } + + startSessionRequest(req, res) { + var user = req.user + var libraryItem = req.libraryItem + var options = req.query + const session = this.startSession(user, libraryItem, options) + res.json(session) + } + + startSession(user, libraryItem, options) { + // TODO: Determine what play method to use and setup playback session + const newPlaybackSession = new PlaybackSession() + this.sessions.push(newPlaybackSession) + return newPlaybackSession } } module.exports = PlaybackSessionManager \ No newline at end of file diff --git a/server/RssFeeds.js b/server/RssFeeds.js index 7c1a4b44..456fb012 100644 --- a/server/RssFeeds.js +++ b/server/RssFeeds.js @@ -1,6 +1,6 @@ -const Podcast = require('podcast') +// const Podcast = require('podcast') const express = require('express') -const ip = require('ip') +// const ip = require('ip') const Logger = require('./Logger') // Not functional at the moment - just an idea @@ -29,29 +29,31 @@ class RssFeeds { } openFeed(audiobook) { - var ipAddress = ip.address('public', 'ipv4') - var serverAddress = 'http://' + ipAddress + ':' + this.Port - Logger.info('Open RSS Feed', 'Server address', serverAddress) + // Removed Podcast npm package and ip package + return null + // var ipAddress = ip.address('public', 'ipv4') + // var serverAddress = 'http://' + ipAddress + ':' + this.Port + // Logger.info('Open RSS Feed', 'Server address', serverAddress) - var feedId = (Date.now() + Math.floor(Math.random() * 1000)).toString(36) - const feed = new Podcast({ - title: audiobook.title, - description: 'AudioBookshelf RSS Feed', - feed_url: `${serverAddress}/feeds/${feedId}`, - image_url: `${serverAddress}/Logo.png`, - author: 'advplyr', - language: 'en' - }) - audiobook.tracks.forEach((track) => { - feed.addItem({ - title: `Track ${track.index}`, - description: `AudioBookshelf Audiobook Track #${track.index}`, - url: `${serverAddress}/feeds/${feedId}?track=${track.index}`, - author: 'advplyr' - }) - }) - this.feeds[feedId] = feed - return feed + // var feedId = (Date.now() + Math.floor(Math.random() * 1000)).toString(36) + // const feed = new Podcast({ + // title: audiobook.title, + // description: 'AudioBookshelf RSS Feed', + // feed_url: `${serverAddress}/feeds/${feedId}`, + // image_url: `${serverAddress}/Logo.png`, + // author: 'advplyr', + // language: 'en' + // }) + // audiobook.tracks.forEach((track) => { + // feed.addItem({ + // title: `Track ${track.index}`, + // description: `AudioBookshelf Audiobook Track #${track.index}`, + // url: `${serverAddress}/feeds/${feedId}?track=${track.index}`, + // author: 'advplyr' + // }) + // }) + // this.feeds[feedId] = feed + // return feed } } module.exports = RssFeeds \ No newline at end of file diff --git a/server/Server.js b/server/Server.js index bd8403d1..a58a8851 100644 --- a/server/Server.js +++ b/server/Server.js @@ -24,7 +24,8 @@ const BackupManager = require('./BackupManager') const LogManager = require('./LogManager') const ApiController = require('./ApiController') const HlsController = require('./HlsController') -const StreamManager = require('./StreamManager') +// const StreamManager = require('./objects/legacy/StreamManager') +const PlaybackSessionManager = require('./PlaybackSessionManager') const DownloadManager = require('./DownloadManager') const CoverController = require('./CoverController') const CacheManager = require('./CacheManager') @@ -58,10 +59,11 @@ class Server { this.coverController = new CoverController(this.db, this.cacheManager) this.scanner = new Scanner(this.db, this.coverController, this.emitter.bind(this)) - this.streamManager = new StreamManager(this.db, this.emitter.bind(this), this.clientEmitter.bind(this)) + this.playbackSessionManager = new PlaybackSessionManager(this.db, this.emitter.bind(this), this.clientEmitter.bind(this)) + // this.streamManager = new StreamManager(this.db, this.emitter.bind(this), this.clientEmitter.bind(this)) this.downloadManager = new DownloadManager(this.db) - this.apiController = new ApiController(this.db, this.auth, this.scanner, this.streamManager, this.downloadManager, this.coverController, this.backupManager, this.watcher, this.cacheManager, this.emitter.bind(this), this.clientEmitter.bind(this)) - this.hlsController = new HlsController(this.db, this.auth, this.streamManager, this.emitter.bind(this), this.streamManager.StreamsPath) + this.apiController = new ApiController(this.db, this.auth, this.scanner, this.playbackSessionManager, this.downloadManager, this.coverController, this.backupManager, this.watcher, this.cacheManager, this.emitter.bind(this), this.clientEmitter.bind(this)) + this.hlsController = new HlsController(this.db, this.auth, this.playbackSessionManager, this.emitter.bind(this)) Logger.logManager = this.logManager @@ -72,8 +74,9 @@ class Server { } get usersOnline() { + // TODO: Map open user sessions return Object.values(this.clients).filter(c => c.user).map(client => { - return client.user.toJSONForPublic(this.streamManager.streams) + return client.user.toJSONForPublic([]) }) } @@ -104,8 +107,9 @@ class Server { async init() { Logger.info('[Server] Init v' + version) - await this.streamManager.ensureStreamsDir() - await this.streamManager.removeOrphanStreams() + // TODO: Remove orphan streams from playback session manager + // await this.streamManager.ensureStreamsDir() + // await this.streamManager.removeOrphanStreams() await this.downloadManager.removeOrphanDownloads() if (version.localeCompare('1.7.3') < 0) { // Old version data model migration @@ -264,9 +268,9 @@ class Server { socket.on('save_metadata', (libraryItemId) => this.saveMetadata(socket, libraryItemId)) // Streaming (only still used in the mobile app) - socket.on('open_stream', (audiobookId) => this.streamManager.openStreamSocketRequest(socket, audiobookId)) - socket.on('close_stream', () => this.streamManager.closeStreamRequest(socket)) - socket.on('stream_sync', (syncData) => this.streamManager.streamSync(socket, syncData)) + // socket.on('open_stream', (audiobookId) => this.streamManager.openStreamSocketRequest(socket, audiobookId)) + // socket.on('close_stream', () => this.streamManager.closeStreamRequest(socket)) + // socket.on('stream_sync', (syncData) => this.streamManager.streamSync(socket, syncData)) // Used to sync when playing local book on mobile, will be moved to API route socket.on('progress_update', (payload) => this.audiobookProgressUpdate(socket, payload)) @@ -299,7 +303,7 @@ class Server { delete this.clients[socket.id] } else { Logger.debug('[Server] User Offline ' + _client.user.username) - this.io.emit('user_offline', _client.user.toJSONForPublic(this.streamManager.streams)) + this.io.emit('user_offline', _client.user.toJSONForPublic([])) const disconnectTime = Date.now() - _client.connected_at Logger.info(`[Server] Socket ${socket.id} disconnected from client "${_client.user.username}" after ${disconnectTime}ms`) @@ -603,16 +607,16 @@ class Server { // Check if user has stream open if (client.user.stream) { Logger.info('User has stream open already', client.user.stream) - client.stream = this.streamManager.getStream(client.user.stream) - if (!client.stream) { - Logger.error('Invalid user stream id', client.user.stream) - this.streamManager.removeOrphanStreamFiles(client.user.stream) - await this.db.updateUserStream(client.user.id, null) - } + // client.stream = this.streamManager.getStream(client.user.stream) + // if (!client.stream) { + // Logger.error('Invalid user stream id', client.user.stream) + // this.streamManager.removeOrphanStreamFiles(client.user.stream) + // await this.db.updateUserStream(client.user.id, null) + // } } Logger.debug(`[Server] User Online ${client.user.username}`) - this.io.emit('user_online', client.user.toJSONForPublic(this.streamManager.streams)) + this.io.emit('user_online', client.user.toJSONForPublic([])) user.lastSeen = Date.now() await this.db.updateEntity('user', user) diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 741a13a7..b234ef32 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -138,7 +138,13 @@ class LibraryItemController { // GET: api/items/:id/stream openStream(req, res) { - this.streamManager.openStreamApiRequest(res, req.user, req.libraryItem) + // this.streamManager.openStreamApiRequest(res, req.user, req.libraryItem) + res.sendStatus(500) + } + + // GET: api/items/:id/play + startPlaybackSession(req, res) { + res.sendStatus(200) } // POST api/items/:id/match diff --git a/server/objects/user/PlaybackSession.js b/server/objects/PlaybackSession.js similarity index 92% rename from server/objects/user/PlaybackSession.js rename to server/objects/PlaybackSession.js index 159cf378..f6034920 100644 --- a/server/objects/user/PlaybackSession.js +++ b/server/objects/PlaybackSession.js @@ -1,8 +1,8 @@ const date = require('date-and-time') -const { getId } = require('../../utils/index') -const { PlayMethod } = require('../../utils/constants') -const BookMetadata = require('../metadata/BookMetadata') -const PodcastMetadata = require('../metadata/PodcastMetadata') +const { getId } = require('../utils/index') +const { PlayMethod } = require('../utils/constants') +const BookMetadata = require('./metadata/BookMetadata') +const PodcastMetadata = require('./metadata/PodcastMetadata') class PlaybackSession { constructor(session) { diff --git a/server/StreamManager.js b/server/objects/legacy/StreamManager.js similarity index 98% rename from server/StreamManager.js rename to server/objects/legacy/StreamManager.js index 9cbe4865..7ae6c3f4 100644 --- a/server/StreamManager.js +++ b/server/objects/legacy/StreamManager.js @@ -1,6 +1,6 @@ -const Stream = require('./objects/Stream') +const Stream = require('../Stream') // const StreamTest = require('./test/StreamTest') -const Logger = require('./Logger') +const Logger = require('../../Logger') const fs = require('fs-extra') const Path = require('path') diff --git a/server/utils/dbMigration.js b/server/utils/dbMigration.js index 5ef8b222..df9e65f1 100644 --- a/server/utils/dbMigration.js +++ b/server/utils/dbMigration.js @@ -19,7 +19,7 @@ const LibraryFile = require('../objects/files/LibraryFile') const FileMetadata = require('../objects/metadata/FileMetadata') const AudioMetaTags = require('../objects/metadata/AudioMetaTags') const LibraryItemProgress = require('../objects/user/LibraryItemProgress') -const PlaybackSession = require('../objects/user/PlaybackSession') +const PlaybackSession = require('../objects/PlaybackSession') const { isObject } = require('.') const User = require('../objects/user/User')