Feat: Redis cache #129

Merged
KevinMidboe merged 14 commits from feat/redis-cache into master 2020-04-08 21:04:07 +00:00
Showing only changes of commit 74340afd16 - Show all commits

View File

@@ -12,58 +12,25 @@ const redisCache = new RedisCache()
// TODO? import class definitions to compare types ? // TODO? import class definitions to compare types ?
// what would typescript do? // what would typescript do?
const matchingTitleOrName = (plex, tmdb) => {
if (plex['title'] !== undefined && tmdb['title'] !== undefined)
return sanitize(plex.title) === sanitize(tmdb.title)
return false
}
const matchingYear = (plex, tmdb) => {
return plex.year === tmdb.year
}
const sanitize = (string) => string.toLowerCase() const sanitize = (string) => string.toLowerCase()
class Plex { const matchingTitleAndYear = (plex, tmdb) => {
constructor(ip, port=32400, cache=null) { let matchingTitle, matchingYear;
this.plexIP = ip
this.plexPort = port
this.cache = cache || redisCache; if (plex['title'] != null && tmdb['title'] != null)
this.cacheTags = { matchingTitle = sanitize(plex.title) == sanitize(tmdb.title);
search: 's' else
} matchingTitle = false;
if (plex['year'] != null && tmdb['year'] != null)
matchingYear = plex.year == tmdb.year;
else
matchingYear = false;
return matchingTitle && matchingYear
} }
matchTmdbAndPlexMedia(plex, tmdb) { const successfullResponse = (response) => {
if (plex === undefined || tmdb === undefined)
return false
let titleMatches;
let yearMatches;
if (plex instanceof Array) {
const plexList = plex
titleMatches = plexList.map(plexItem => matchingTitleOrName(plexItem, tmdb))
yearMatches = plexList.map(plexItem => matchingYear(plexItem, tmdb))
titleMatches = titleMatches.includes(true)
yearMatches = yearMatches.includes(true)
} else {
titleMatches = matchingTitleOrName(plex, tmdb)
yearMatches = matchingYear(plex, tmdb)
}
return titleMatches && yearMatches
}
existsInPlex(tmdbMovie) {
return this.search(tmdbMovie.title)
.then(plexMovies => plexMovies.some(plex => this.matchTmdbAndPlexMedia(plex, tmdbMovie)))
}
successfullResponse(response) {
const { status, statusText } = response const { status, statusText } = response
if (status === 200) { if (status === 200) {
@@ -73,24 +40,118 @@ class Plex {
} }
} }
search(query) {
query = encodeURIComponent(query) class Plex {
const url = `http://${this.plexIP}:${this.plexPort}/hubs/search?query=${query}` constructor(ip, port=32400, cache=null) {
this.plexIP = ip
this.plexPort = port
this.cache = cache || redisCache;
this.cacheTags = {
machineInfo: 'mi',
search: 's'
}
}
fetchMachineIdentifier() {
const cacheKey = `plex/${this.cacheTags.machineInfo}`
const url = `http://${this.plexIP}:${this.plexPort}/`
const options = { const options = {
timeout: 2000, timeout: 20000,
headers: { 'Accept': 'application/json' } headers: { 'Accept': 'application/json' }
} }
return fetch(url, options) return new Promise((resolve, reject) => this.cache.get(cacheKey)
.then(this.successfullResponse) .then(machineInfo => resolve(machineInfo['machineIdentifier']))
.then(this.mapResults) .catch(() => fetch(url, options))
.then(response => response.json())
.then(machineInfo => this.cache.set(cacheKey, machineInfo['MediaContainer'], 2628000))
.then(machineInfo => resolve(machineInfo['machineIdentifier']))
.catch(error => { .catch(error => {
if (error.type === 'request-timeout') { if (error != undefined && error.type === 'request-timeout') {
throw { message: 'Plex did not respond', status: 408, success: false } reject({ message: 'Plex did not respond', status: 408, success: false })
} }
throw error reject(error)
}) })
)
}
matchTmdbAndPlexMedia(plex, tmdb) {
let match;
if (plex == null || tmdb == null)
return false
if (plex instanceof Array) {
let possibleMatches = plex.map(plexItem => matchingTitleAndYear(plexItem, tmdb))
match = possibleMatches.includes(true)
} else {
match = matchingTitleAndYear(plex, tmdb)
}
return match
}
existsInPlex(tmdb) {
const plexMatch = this.findPlexItemByTitleAndYear(tmdb.title, tmdb.year)
return plexMatch ? true : false
}
findPlexItemByTitleAndYear(title, year) {
const query = { title, year }
return this.search(query.title)
.then(plexSearchResults => {
const matchesInPlex = plexSearchResults.map(plex => this.matchTmdbAndPlexMedia(plex, query))
if (matchesInPlex.includes(true) === false)
return false;
const firstMatchIndex = matchesInPlex.indexOf(true)
return plexSearchResults[firstMatchIndex][0]
})
}
getDirectLinkByTitleAndYear(title, year) {
const machineIdentifierPromise = this.fetchMachineIdentifier()
const matchingObjectInPlexPromise = this.findPlexItemByTitleAndYear(title, year)
return Promise.all([machineIdentifierPromise, matchingObjectInPlexPromise])
.then(([machineIdentifier, matchingObjectInPlex]) => {
if (matchingObjectInPlex == false || matchingObjectInPlex == null ||
matchingObjectInPlex['key'] == null || machineIdentifier == null)
return false
const keyUriComponent = encodeURIComponent(matchingObjectInPlex.key)
return `https://app.plex.tv/desktop#!/server/${machineIdentifier}/details?key=${keyUriComponent}`
})
}
search(query) {
const cacheKey = `plex/${this.cacheTags.search}:${query}`
const url = `http://${this.plexIP}:${this.plexPort}/hubs/search?query=${query}`
const options = {
timeout: 20000,
headers: { 'Accept': 'application/json' }
}
return new Promise((resolve, reject) => this.cache.get(cacheKey)
.then(resolve) // if found in cache resolve
.catch(() => fetch(url, options)) // else fetch fresh data
.then(successfullResponse)
.then(this.mapResults)
.then(results => this.cache.set(cacheKey, results, 600))
.then(resolve)
.catch(error => {
if (error != undefined && error.type === 'request-timeout') {
reject({ message: 'Plex did not respond', status: 408, success: false })
}
reject(error)
})
)
} }
mapResults(response) { mapResults(response) {
@@ -103,10 +164,7 @@ class Plex {
.filter(category => category.size > 0) .filter(category => category.size > 0)
.map(category => { .map(category => {
if (category.type === 'movie') { if (category.type === 'movie') {
return category.Metadata.map(movie => { return category.Metadata
const ovie = Movie.convertFromPlexResponse(movie)
return ovie.createJsonResponse()
})
} else if (category.type === 'show') { } else if (category.type === 'show') {
return category.Metadata.map(convertPlexToShow) return category.Metadata.map(convertPlexToShow)
} else if (category.type === 'episode') { } else if (category.type === 'episode') {
@@ -114,7 +172,7 @@ class Plex {
} }
}) })
.filter(result => result !== undefined) .filter(result => result !== undefined)
.flat() // .flat()
} }
} }