fix: resolved issues with the music slider displaying all menus, and ensured media are properly removed from Lidarr.
This commit is contained in:
@@ -171,7 +171,7 @@ class Media {
|
||||
})
|
||||
public mediaAddedAt: Date;
|
||||
|
||||
@Column({ nullable: true, type: 'int' })
|
||||
@Column({ nullable: false, type: 'int', default: 0 })
|
||||
public serviceId?: number | null;
|
||||
|
||||
@Column({ nullable: true, type: 'int' })
|
||||
|
||||
@@ -566,26 +566,13 @@ class BaseScanner<T> {
|
||||
? MediaStatus.PROCESSING
|
||||
: MediaStatus.AVAILABLE;
|
||||
newMedia.mediaType = MediaType.MUSIC;
|
||||
|
||||
if (mediaAddedAt) {
|
||||
newMedia.mediaAddedAt = mediaAddedAt;
|
||||
}
|
||||
|
||||
if (ratingKey) {
|
||||
newMedia.ratingKey = ratingKey;
|
||||
}
|
||||
|
||||
if (serviceId) {
|
||||
newMedia.serviceId = serviceId;
|
||||
}
|
||||
|
||||
if (externalServiceId) {
|
||||
newMedia.externalServiceId = externalServiceId;
|
||||
}
|
||||
|
||||
if (externalServiceSlug) {
|
||||
newMedia.externalServiceSlug = externalServiceSlug;
|
||||
}
|
||||
newMedia.mediaAddedAt = mediaAddedAt ?? newMedia.mediaAddedAt;
|
||||
newMedia.ratingKey = ratingKey ?? newMedia.ratingKey;
|
||||
newMedia.serviceId = serviceId ?? newMedia.serviceId;
|
||||
newMedia.externalServiceId =
|
||||
externalServiceId ?? newMedia.externalServiceId;
|
||||
newMedia.externalServiceSlug =
|
||||
externalServiceSlug ?? newMedia.externalServiceSlug;
|
||||
|
||||
try {
|
||||
await mediaRepository.save(newMedia);
|
||||
@@ -596,6 +583,41 @@ class BaseScanner<T> {
|
||||
error: err.message,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let hasChanges = false;
|
||||
|
||||
if (serviceId && !existing.serviceId) {
|
||||
existing.serviceId = serviceId;
|
||||
hasChanges = true;
|
||||
}
|
||||
if (externalServiceId && !existing.externalServiceId) {
|
||||
existing.externalServiceId = externalServiceId;
|
||||
hasChanges = true;
|
||||
}
|
||||
if (externalServiceSlug && !existing.externalServiceSlug) {
|
||||
existing.externalServiceSlug = externalServiceSlug;
|
||||
hasChanges = true;
|
||||
}
|
||||
if (mediaAddedAt && !existing.mediaAddedAt) {
|
||||
existing.mediaAddedAt = mediaAddedAt;
|
||||
hasChanges = true;
|
||||
}
|
||||
if (ratingKey && !existing.ratingKey) {
|
||||
existing.ratingKey = ratingKey;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
try {
|
||||
await mediaRepository.save(existing);
|
||||
this.log(`Updated existing media: ${title}`);
|
||||
} catch (err) {
|
||||
this.log('Failed to update existing media', 'error', {
|
||||
title,
|
||||
error: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ class LidarrScanner
|
||||
const sessionId = this.startRun();
|
||||
|
||||
try {
|
||||
// Filter out duplicate servers
|
||||
this.servers = uniqWith(settings.lidarr, (lidarrA, lidarrB) => {
|
||||
return (
|
||||
lidarrA.hostname === lidarrB.hostname &&
|
||||
@@ -81,14 +80,10 @@ class LidarrScanner
|
||||
private async processLidarrAlbum(lidarrAlbum: LidarrAlbum): Promise<void> {
|
||||
try {
|
||||
if (!lidarrAlbum.monitored) {
|
||||
this.log('Title is unmonitored. Skipping item.', 'debug', {
|
||||
title: lidarrAlbum.title,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const mbId = lidarrAlbum.foreignAlbumId;
|
||||
|
||||
if (!mbId) {
|
||||
this.log(
|
||||
'No MusicBrainz ID found for this title. Skipping item.',
|
||||
@@ -103,7 +98,7 @@ class LidarrScanner
|
||||
await this.processMusic(mbId, {
|
||||
serviceId: this.currentServer.id,
|
||||
externalServiceId: lidarrAlbum.id,
|
||||
externalServiceSlug: lidarrAlbum.titleSlug,
|
||||
externalServiceSlug: mbId,
|
||||
title: lidarrAlbum.title,
|
||||
processing:
|
||||
lidarrAlbum.monitored &&
|
||||
|
||||
@@ -26,12 +26,15 @@ musicRoutes.get('/:id', async (req, res, next) => {
|
||||
|
||||
const [media, onUserWatchlist] = await Promise.all([
|
||||
getRepository(Media)
|
||||
.findOne({
|
||||
where: {
|
||||
mbId: req.params.id,
|
||||
mediaType: MediaType.MUSIC,
|
||||
},
|
||||
.createQueryBuilder('media')
|
||||
.leftJoinAndSelect('media.requests', 'requests')
|
||||
.leftJoinAndSelect('requests.requestedBy', 'requestedBy')
|
||||
.leftJoinAndSelect('requests.modifiedBy', 'modifiedBy')
|
||||
.where({
|
||||
mbId: req.params.id,
|
||||
mediaType: MediaType.MUSIC,
|
||||
})
|
||||
.getOne()
|
||||
.then((media) => media ?? undefined),
|
||||
|
||||
getRepository(Watchlist).exist({
|
||||
|
||||
@@ -738,6 +738,226 @@ export class MediaRequestSubscriber
|
||||
}
|
||||
}
|
||||
|
||||
public async sendToLidarr(): Promise<void> {
|
||||
if (
|
||||
this.status !== MediaRequestStatus.APPROVED ||
|
||||
this.type !== MediaType.MUSIC
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const mediaRepository = getRepository(Media);
|
||||
const settings = getSettings();
|
||||
const media = await mediaRepository.findOne({
|
||||
where: { id: this.media.id },
|
||||
relations: { requests: true },
|
||||
});
|
||||
|
||||
if (!media?.mbId) {
|
||||
throw new Error('Media data or MusicBrainz ID not found');
|
||||
}
|
||||
|
||||
const lidarrSettings =
|
||||
this.serverId !== null && this.serverId >= 0
|
||||
? settings.lidarr.find((l) => l.id === this.serverId)
|
||||
: settings.lidarr.find((l) => l.isDefault);
|
||||
|
||||
if (!lidarrSettings) {
|
||||
logger.warn('No valid Lidarr server configured', {
|
||||
label: 'Media Request',
|
||||
requestId: this.id,
|
||||
mediaId: this.media.id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const rootFolder = this.rootFolder || lidarrSettings.activeDirectory;
|
||||
const qualityProfile = this.profileId || lidarrSettings.activeProfileId;
|
||||
const tags = lidarrSettings.tags?.map((t) => t.toString()) || [];
|
||||
|
||||
const lidarr = new LidarrAPI({
|
||||
apiKey: lidarrSettings.apiKey,
|
||||
url: LidarrAPI.buildUrl(lidarrSettings, '/api/v1'),
|
||||
});
|
||||
|
||||
const lidarrAlbum = await lidarr.getAlbumByMusicBrainzId(media.mbId);
|
||||
|
||||
let artistId: number;
|
||||
try {
|
||||
const existingArtist = await lidarr.getArtistByMusicBrainzId(
|
||||
lidarrAlbum.artist.foreignArtistId
|
||||
);
|
||||
artistId = existingArtist.id;
|
||||
} catch {
|
||||
const addedArtist = await lidarr.addArtist({
|
||||
artistName: lidarrAlbum.artist.artistName,
|
||||
foreignArtistId: lidarrAlbum.artist.foreignArtistId,
|
||||
qualityProfileId: qualityProfile,
|
||||
profileId: qualityProfile,
|
||||
metadataProfileId: qualityProfile,
|
||||
rootFolderPath: rootFolder,
|
||||
monitored: false,
|
||||
tags: tags.map((t) => Number(t)),
|
||||
searchNow: !lidarrSettings.preventSearch,
|
||||
monitorNewItems: 'none',
|
||||
monitor: 'none',
|
||||
searchForMissingAlbums: false,
|
||||
addOptions: {
|
||||
monitor: 'none',
|
||||
monitored: false,
|
||||
searchForMissingAlbums: false,
|
||||
},
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 60000));
|
||||
artistId = addedArtist.id;
|
||||
}
|
||||
|
||||
try {
|
||||
const album = await lidarr.addAlbum({
|
||||
mbId: media.mbId,
|
||||
foreignAlbumId: media.mbId,
|
||||
title: lidarrAlbum.title,
|
||||
qualityProfileId: qualityProfile,
|
||||
profileId: qualityProfile,
|
||||
metadataProfileId: qualityProfile,
|
||||
rootFolderPath: rootFolder,
|
||||
monitored: false,
|
||||
tags,
|
||||
searchNow: false,
|
||||
artistId,
|
||||
images: lidarrAlbum.images?.length
|
||||
? lidarrAlbum.images
|
||||
: [
|
||||
{
|
||||
url: '',
|
||||
coverType: 'cover',
|
||||
},
|
||||
],
|
||||
addOptions: {
|
||||
monitor: 'none',
|
||||
monitored: false,
|
||||
searchForMissingAlbums: false,
|
||||
},
|
||||
artist: {
|
||||
id: artistId,
|
||||
foreignArtistId: lidarrAlbum.artist.foreignArtistId,
|
||||
artistName: lidarrAlbum.artist.artistName,
|
||||
qualityProfileId: qualityProfile,
|
||||
metadataProfileId: qualityProfile,
|
||||
rootFolderPath: rootFolder,
|
||||
monitored: false,
|
||||
monitorNewItems: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
media.externalServiceId = album.id;
|
||||
(media.externalServiceSlug = media.mbId),
|
||||
(media.serviceId = lidarrSettings.id);
|
||||
media.status = MediaStatus.PROCESSING;
|
||||
await mediaRepository.save(media);
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const albumDetails = await lidarr.getAlbum({ id: album.id });
|
||||
albumDetails.monitored = true;
|
||||
await lidarr.updateAlbum(albumDetails);
|
||||
|
||||
if (!lidarrSettings.preventSearch) {
|
||||
await lidarr.searchAlbum(album.id);
|
||||
}
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const finalAlbumDetails = await lidarr.getAlbum({
|
||||
id: album.id,
|
||||
});
|
||||
if (!finalAlbumDetails.monitored) {
|
||||
finalAlbumDetails.monitored = true;
|
||||
await lidarr.updateAlbum(finalAlbumDetails);
|
||||
await lidarr.searchAlbum(album.id);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Failed final album monitoring check', {
|
||||
label: 'Media Request',
|
||||
error: err.message,
|
||||
requestId: this.id,
|
||||
mediaId: this.media.id,
|
||||
albumId: album.id,
|
||||
});
|
||||
}
|
||||
}, 20000);
|
||||
|
||||
logger.info('Completed album monitoring setup', {
|
||||
label: 'Media Request',
|
||||
requestId: this.id,
|
||||
mediaId: this.media.id,
|
||||
albumId: album.id,
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('Failed to process album monitoring', {
|
||||
label: 'Media Request',
|
||||
error: err.message,
|
||||
requestId: this.id,
|
||||
mediaId: this.media.id,
|
||||
albumId: album.id,
|
||||
});
|
||||
}
|
||||
}, 60000);
|
||||
} catch (error) {
|
||||
if (error.message.includes('This album has already been added')) {
|
||||
const existingAlbums = await lidarr.getAlbums();
|
||||
const existingAlbum = existingAlbums.find(
|
||||
(a) => a.foreignAlbumId === media.mbId
|
||||
);
|
||||
|
||||
if (existingAlbum) {
|
||||
media.externalServiceId = existingAlbum.id;
|
||||
media.externalServiceSlug = media.mbId;
|
||||
media.serviceId = lidarrSettings.id;
|
||||
media.status = MediaStatus.PROCESSING;
|
||||
await mediaRepository.save(media);
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 20000));
|
||||
const albumDetails = await lidarr.getAlbum({
|
||||
id: existingAlbum.id,
|
||||
});
|
||||
albumDetails.monitored = true;
|
||||
await lidarr.updateAlbum(albumDetails);
|
||||
|
||||
if (!lidarrSettings.preventSearch) {
|
||||
await lidarr.searchAlbum(existingAlbum.id);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Failed to process existing album', {
|
||||
label: 'Media Request',
|
||||
error: err.message,
|
||||
requestId: this.id,
|
||||
mediaId: this.media.id,
|
||||
albumId: existingAlbum.id,
|
||||
});
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
} else {
|
||||
const requestRepository = getRepository(MediaRequest);
|
||||
this.status = MediaRequestStatus.FAILED;
|
||||
await requestRepository.save(this);
|
||||
this.sendNotification(media, Notification.MEDIA_FAILED);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Failed to process Lidarr request', {
|
||||
label: 'Media Request',
|
||||
error: e.message,
|
||||
requestId: this.id,
|
||||
mediaId: this.media.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
public async updateParentStatus(entity: MediaRequest): Promise<void> {
|
||||
const mediaRepository = getRepository(Media);
|
||||
const media = await mediaRepository.findOne({
|
||||
|
||||
@@ -68,6 +68,7 @@ const messages = defineMessages('components.ManageSlideOver', {
|
||||
playedby: 'Played By',
|
||||
movie: 'movie',
|
||||
tvshow: 'series',
|
||||
album: 'album',
|
||||
});
|
||||
|
||||
const isMovie = (
|
||||
@@ -515,9 +516,16 @@ const ManageSlideOver = ({
|
||||
mediaType: intl.formatMessage(
|
||||
mediaType === 'movie'
|
||||
? messages.movie
|
||||
: mediaType === 'music'
|
||||
? messages.album
|
||||
: messages.tvshow
|
||||
),
|
||||
arr: mediaType === 'movie' ? 'Radarr' : 'Sonarr',
|
||||
arr:
|
||||
mediaType === 'movie'
|
||||
? 'Radarr'
|
||||
: mediaType === 'music'
|
||||
? 'Lidarr'
|
||||
: 'Sonarr',
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
@@ -742,7 +750,11 @@ const ManageSlideOver = ({
|
||||
<div className="mt-2 text-xs text-gray-400">
|
||||
{intl.formatMessage(messages.manageModalClearMediaWarning, {
|
||||
mediaType: intl.formatMessage(
|
||||
mediaType === 'movie' ? messages.movie : messages.tvshow
|
||||
mediaType === 'movie'
|
||||
? messages.movie
|
||||
: mediaType === 'music'
|
||||
? messages.album
|
||||
: messages.tvshow
|
||||
),
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType ===
|
||||
|
||||
@@ -78,20 +78,23 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
|
||||
|
||||
return (
|
||||
<div className="block">
|
||||
<RequestModal
|
||||
show={showEditModal}
|
||||
tmdbId={request.media.tmdbId}
|
||||
type={request.type}
|
||||
is4k={request.is4k}
|
||||
editRequest={request}
|
||||
onCancel={() => setShowEditModal(false)}
|
||||
onComplete={() => {
|
||||
if (onUpdate) {
|
||||
onUpdate();
|
||||
}
|
||||
setShowEditModal(false);
|
||||
}}
|
||||
/>
|
||||
{request.media && (
|
||||
<RequestModal
|
||||
show={showEditModal}
|
||||
tmdbId={request.type === 'music' ? undefined : request.media.tmdbId}
|
||||
mbId={request.type === 'music' ? request.media.mbId : undefined}
|
||||
type={request.type}
|
||||
is4k={request.is4k}
|
||||
editRequest={request}
|
||||
onCancel={() => setShowEditModal(false)}
|
||||
onComplete={() => {
|
||||
if (onUpdate) {
|
||||
onUpdate();
|
||||
}
|
||||
setShowEditModal(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="px-4 py-3 text-gray-300">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="mr-6 min-w-0 flex-1 flex-col items-center text-sm leading-5">
|
||||
|
||||
Reference in New Issue
Block a user