using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; using ErsatzTV.Infrastructure.Plex.Models; using Microsoft.IO; namespace ErsatzTV.Infrastructure.Plex; [SuppressMessage("Security", "CA5350:Do Not Use Weak Cryptographic Algorithms")] public class PlexEtag { private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; public PlexEtag(RecyclableMemoryStreamManager recyclableMemoryStreamManager) => _recyclableMemoryStreamManager = recyclableMemoryStreamManager; public string ForMovie(PlexMetadataResponse response) { using MemoryStream ms = _recyclableMemoryStreamManager.GetStream(); using var bw = new BinaryWriter(ms); // video key bw.Write(response.Key); // video added at bw.Write(response.AddedAt); // video updated at bw.Write(response.UpdatedAt); foreach (PlexMediaResponse media in response.Media) { // media id bw.Write((byte)FieldKey.MediaId); bw.Write(media.Id); // media part id foreach (PlexPartResponse part in media.Part) { bw.Write((byte)FieldKey.Key); bw.Write(part.Key); bw.Write((byte)FieldKey.PartId); bw.Write(part.Id); bw.Write((byte)FieldKey.File); bw.Write(part.File); bw.Write((byte)FieldKey.Duration); bw.Write(part.Duration); bw.Write((byte)FieldKey.Size); bw.Write(part.Size); bw.Write((byte)FieldKey.Container); bw.Write(part.Container ?? string.Empty); bw.Write((byte)FieldKey.VideoProfile); bw.Write(part.VideoProfile ?? string.Empty); bw.Write((byte)FieldKey.AudioProfile); bw.Write(part.AudioProfile ?? string.Empty); } } // genre ids foreach (PlexGenreResponse genre in Optional(response.Genre).Flatten()) { bw.Write((byte)FieldKey.GenreTag); bw.Write(genre.Tag); } // label ids foreach (PlexLabelResponse label in Optional(response.Label).Flatten()) { bw.Write((byte)FieldKey.LabelTag); bw.Write(label.Tag); } // director ids foreach (PlexDirectorResponse director in Optional(response.Director).Flatten()) { bw.Write((byte)FieldKey.DirectorTag); bw.Write(director.Tag); } // writer ids foreach (PlexWriterResponse writer in Optional(response.Writer).Flatten()) { bw.Write((byte)FieldKey.WriterTag); bw.Write(writer.Tag); } // collection ids foreach (PlexCollectionResponse collection in Optional(response.Collection).Flatten()) { bw.Write((byte)FieldKey.CollectionTag); bw.Write(collection.Tag); } // role ids foreach (PlexRoleResponse role in Optional(response.Role).Flatten()) { bw.Write((byte)FieldKey.RoleTag); bw.Write(role.Tag); } ms.Position = 0; byte[] hash = SHA1.Create().ComputeHash(ms); return BitConverter.ToString(hash).Replace("-", string.Empty); } public string ForShow(PlexMetadataResponse response) { using MemoryStream ms = _recyclableMemoryStreamManager.GetStream(); using var bw = new BinaryWriter(ms); // video key bw.Write(response.Key); // video added at bw.Write(response.AddedAt); // video updated at bw.Write(response.UpdatedAt); // genre ids foreach (PlexGenreResponse genre in Optional(response.Genre).Flatten()) { bw.Write((byte)FieldKey.GenreTag); bw.Write(genre.Tag); } // collection ids foreach (PlexCollectionResponse collection in Optional(response.Collection).Flatten()) { bw.Write((byte)FieldKey.CollectionTag); bw.Write(collection.Tag); } // role ids foreach (PlexRoleResponse role in Optional(response.Role).Flatten()) { bw.Write((byte)FieldKey.RoleTag); bw.Write(role.Tag); } // label ids foreach (PlexLabelResponse label in Optional(response.Label).Flatten()) { bw.Write((byte)FieldKey.LabelTag); bw.Write(label.Tag); } ms.Position = 0; byte[] hash = SHA1.Create().ComputeHash(ms); return BitConverter.ToString(hash).Replace("-", string.Empty); } public string ForSeason(PlexXmlMetadataResponse response) { using MemoryStream ms = _recyclableMemoryStreamManager.GetStream(); using var bw = new BinaryWriter(ms); // video key bw.Write(response.Key); // video added at bw.Write(response.AddedAt); // video updated at bw.Write(response.UpdatedAt); // collection ids foreach (PlexCollectionResponse collection in Optional(response.Collection).Flatten()) { bw.Write((byte)FieldKey.CollectionTag); bw.Write(collection.Tag); } // thumb if (!string.IsNullOrWhiteSpace(response.Thumb)) { bw.Write(response.Thumb); } // art if (!string.IsNullOrWhiteSpace(response.Art)) { bw.Write(response.Art); } ms.Position = 0; byte[] hash = SHA1.Create().ComputeHash(ms); return BitConverter.ToString(hash).Replace("-", string.Empty); } public string ForEpisode(PlexXmlMetadataResponse response) { using MemoryStream ms = _recyclableMemoryStreamManager.GetStream(); using var bw = new BinaryWriter(ms); // video key bw.Write(response.Key); // video added at bw.Write(response.AddedAt); // video updated at bw.Write(response.UpdatedAt); foreach (PlexMediaResponse media in response.Media) { // media id bw.Write((byte)FieldKey.MediaId); bw.Write(media.Id); // media part id foreach (PlexXmlPartResponse part in media.Part) { bw.Write((byte)FieldKey.Key); bw.Write(part.Key); bw.Write((byte)FieldKey.PartId); bw.Write(part.Id); bw.Write((byte)FieldKey.File); bw.Write(part.File); bw.Write((byte)FieldKey.Duration); bw.Write(part.Duration); bw.Write((byte)FieldKey.Size); bw.Write(part.Size); bw.Write((byte)FieldKey.Container); bw.Write(part.Container ?? string.Empty); bw.Write((byte)FieldKey.VideoProfile); bw.Write(part.VideoProfile ?? string.Empty); bw.Write((byte)FieldKey.AudioProfile); bw.Write(part.AudioProfile ?? string.Empty); // stream id foreach (PlexStreamResponse stream in part.Stream) { bw.Write((byte)FieldKey.StreamId); bw.Write(stream.Id); } } } // genre ids foreach (PlexGenreResponse genre in Optional(response.Genre).Flatten()) { bw.Write((byte)FieldKey.GenreTag); bw.Write(genre.Tag); } // director ids foreach (PlexDirectorResponse director in Optional(response.Director).Flatten()) { bw.Write((byte)FieldKey.DirectorTag); bw.Write(director.Tag); } // writer ids foreach (PlexWriterResponse writer in Optional(response.Writer).Flatten()) { bw.Write((byte)FieldKey.WriterTag); bw.Write(writer.Tag); } // collection ids foreach (PlexCollectionResponse collection in Optional(response.Collection).Flatten()) { bw.Write((byte)FieldKey.CollectionTag); bw.Write(collection.Tag); } // role ids foreach (PlexRoleResponse role in Optional(response.Role).Flatten()) { bw.Write((byte)FieldKey.RoleTag); bw.Write(role.Tag); } ms.Position = 0; byte[] hash = SHA1.Create().ComputeHash(ms); return BitConverter.ToString(hash).Replace("-", string.Empty); } public string ForCollection(PlexCollectionMetadataResponse response) { using MemoryStream ms = _recyclableMemoryStreamManager.GetStream(); using var bw = new BinaryWriter(ms); // collection key bw.Write(response.Key); // collection added at bw.Write(response.AddedAt); // collection updated at bw.Write(response.UpdatedAt); // collection child count bw.Write(response.ChildCount ?? "0"); // collection is smart collection bw.Write(response.Smart ?? "0"); ms.Position = 0; byte[] hash = SHA1.Create().ComputeHash(ms); return BitConverter.ToString(hash).Replace("-", string.Empty); } private enum FieldKey : byte { MediaId = 0, PartId = 1, StreamId = 2, GenreTag = 10, DirectorTag = 11, WriterTag = 12, CollectionTag = 13, RoleTag = 14, LabelTag = 15, Thumb = 20, Art = 21, File = 30, Key = 31, ChildCount = 40, Smart = 41, // smart collection bool Duration = 50, Size = 51, Container = 52, VideoProfile = 53, AudioProfile = 54 } }