diff --git a/SgfDevs.Tests/SessionizeSpeakerMediaServiceTests.cs b/SgfDevs.Tests/SessionizeSpeakerMediaServiceTests.cs new file mode 100644 index 0000000..6112f2c --- /dev/null +++ b/SgfDevs.Tests/SessionizeSpeakerMediaServiceTests.cs @@ -0,0 +1,48 @@ +using System.IO; +using System.Text; +using SgfDevs.Dev.EventSync; +using Xunit; + +namespace SgfDevs.Tests; + +public class SessionizeSpeakerMediaServiceTests +{ + [Fact] + public void StreamsHaveEqualContent_ReturnsTrueForIdenticalContent() + { + using var first = CreateStream("same image"); + using var second = CreateStream("same image"); + + var result = SessionizeSpeakerMediaService.StreamsHaveEqualContent(first, second); + + Assert.True(result); + } + + [Fact] + public void StreamsHaveEqualContent_ReturnsFalseForDifferentContent() + { + using var first = CreateStream("first image"); + using var second = CreateStream("second image"); + + var result = SessionizeSpeakerMediaService.StreamsHaveEqualContent(first, second); + + Assert.False(result); + } + + [Fact] + public void StreamsHaveEqualContent_RestoresFirstStreamPosition() + { + using var first = CreateStream("same image"); + using var second = CreateStream("same image"); + first.Position = 2; + second.Position = 4; + + var result = SessionizeSpeakerMediaService.StreamsHaveEqualContent(first, second); + + Assert.True(result); + Assert.Equal(2, first.Position); + Assert.Equal(4, second.Position); + } + + private static MemoryStream CreateStream(string content) => new(Encoding.UTF8.GetBytes(content)); +} diff --git a/SgfDevs/Dev/EventSync/SessionizeSpeakerMediaService.cs b/SgfDevs/Dev/EventSync/SessionizeSpeakerMediaService.cs index 35772c9..b278e8a 100644 --- a/SgfDevs/Dev/EventSync/SessionizeSpeakerMediaService.cs +++ b/SgfDevs/Dev/EventSync/SessionizeSpeakerMediaService.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -67,6 +68,30 @@ public async Task ImportProfileImageAsync(ImportedPresent var importedSpeakersFolder = GetOrCreateImportedSpeakersFolder(); var media = GetOrCreateSpeakerImage(importedSpeakersFolder.Id, presenter); + var profileImageUdi = new GuidUdi(Constants.UdiEntityType.Media, media.Key).ToString(); + + if (media.HasIdentity) + { + try + { + using var existingStream = _mediaFileManager.GetFile(media, out _); + if (ReferenceEquals(existingStream, Stream.Null) == false && + StreamsHaveEqualContent(stream, existingStream)) + { + return presenter with { ProfileImageUdi = profileImageUdi }; + } + } + catch (Exception exception) + { + _logger.LogWarning( + exception, + "Failed to compare the existing Sessionize speaker image for {SpeakerName} ({SpeakerId}); retaining it.", + presenter.Name, + presenter.SessionizeSpeakerId); + return presenter with { ProfileImageUdi = profileImageUdi }; + } + } + var fileName = BuildFileName(presenter); media.SetValue( @@ -82,7 +107,7 @@ public async Task ImportProfileImageAsync(ImportedPresent return presenter with { - ProfileImageUdi = new GuidUdi(Constants.UdiEntityType.Media, media.Key).ToString() + ProfileImageUdi = profileImageUdi }; } catch (Exception exception) @@ -178,4 +203,39 @@ private static string BuildFileName(ImportedPresenterPlan presenter) return $"{baseName}{extension}"; } + + internal static bool StreamsHaveEqualContent(Stream first, Stream second) + { + var firstPosition = first.CanSeek ? first.Position : 0; + var secondPosition = second.CanSeek ? second.Position : 0; + + try + { + if (first.CanSeek) + { + first.Position = 0; + } + + if (second.CanSeek) + { + second.Position = 0; + } + + var firstHash = SHA256.HashData(first); + var secondHash = SHA256.HashData(second); + return firstHash.AsSpan().SequenceEqual(secondHash); + } + finally + { + if (first.CanSeek) + { + first.Position = firstPosition; + } + + if (second.CanSeek) + { + second.Position = secondPosition; + } + } + } }