Skip to content
This repository has been archived by the owner on Oct 13, 2024. It is now read-only.

Commit

Permalink
feat: track theme file md5 hash (#185)
Browse files Browse the repository at this point in the history
  • Loading branch information
ReenigneArcher authored Dec 16, 2023
1 parent eddfed8 commit 172e61d
Show file tree
Hide file tree
Showing 8 changed files with 229 additions and 25 deletions.
149 changes: 140 additions & 9 deletions Jellyfin.Plugin.Themerr.Tests/TestThemerrManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,44 @@ private List<string> FixtureThemerrDbUrls()
return youtubeUrls;
}

[Fact]
[Trait("Category", "Unit")]
private void TestGetExistingThemerrDataValue()
{
string themerrDataPath;
themerrDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"dummy.json");

// ensure correct values are returned
Assert.Equal(
"dummy_value",
_themerrManager.GetExistingThemerrDataValue("dummy_key", themerrDataPath));
Assert.Equal(
"https://www.youtube.com/watch?v=E8nxMWr2sr4",
_themerrManager.GetExistingThemerrDataValue("youtube_theme_url", themerrDataPath));

// ensure null when the key does not exist
Assert.Null(_themerrManager.GetExistingThemerrDataValue("invalid_key", themerrDataPath));

// ensure null when the file does not exist
themerrDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"no_file.json");

Assert.Null(_themerrManager.GetExistingThemerrDataValue("any_key", themerrDataPath));

// test empty json file
themerrDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"empty.json");

Assert.Null(_themerrManager.GetExistingThemerrDataValue("any_key", themerrDataPath));
}

[Fact]
[Trait("Category", "Unit")]
private void TestSaveMp3()
Expand Down Expand Up @@ -180,15 +218,80 @@ private void TestSaveMp3InvalidUrl()

[Fact]
[Trait("Category", "Unit")]
private void TestShouldSkipDownload()
private void TestContinueDownload()
{
var themePath = Path.Combine(
"theme.mp3");
var themerrDataPath = Path.Combine(
"themerr_data.json");

var shouldSkipDownload = _themerrManager.ShouldSkipDownload(themePath, themerrDataPath);
Assert.False(shouldSkipDownload, "ShouldSkipDownload returned True");
string themePath;
string themerrDataPath;

// test when neither theme nor data file exists
themePath = Path.Combine(
"no_file.mp3");
themerrDataPath = Path.Combine(
"no_file.json");
Assert.True(_themerrManager.ContinueDownload(themePath, themerrDataPath), "ContinueDownload returned False");

// test when theme does not exist and data file does
themePath = Path.Combine(
"no_file.mp3");

// copy the dummy.json to a secondary location
var ogFile = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"dummy.json");
themerrDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"dummy2.json");

// copy the dummy.json
File.Copy(ogFile, themerrDataPath);
Assert.True(_themerrManager.ContinueDownload(themePath, themerrDataPath), "ContinueDownload returned False");
Assert.False(File.Exists(themerrDataPath), $"File {themerrDataPath} was not removed");

// test when theme file exists but data file does not
themePath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"audio_stub.mp3");
themerrDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"no_file.json");
Assert.False(_themerrManager.ContinueDownload(themePath, themerrDataPath), "ContinueDownload returned True");

// test when both theme and data file exist, but hash is empty in data file
themePath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"audio_stub.mp3");
themerrDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"dummy.json");
Assert.True(_themerrManager.ContinueDownload(themePath, themerrDataPath), "ContinueDownload returned False");

// test when both theme and data file exist, and md5 hashes match
themePath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"audio_stub.mp3");
themerrDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"audio_themerr_data.json");
Assert.True(_themerrManager.ContinueDownload(themePath, themerrDataPath), "ContinueDownload returned False");

// test when both theme and data file exist, and md5 hashes do not match
themePath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"audio_stub.mp3");
themerrDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"audio_themerr_data_user_overwritten.json");
Assert.False(_themerrManager.ContinueDownload(themePath, themerrDataPath), "ContinueDownload returned True");
}

[Fact]
Expand Down Expand Up @@ -273,11 +376,16 @@ private void TestSaveThemerrData()
// set mock themerrDataPath using a random number
var mockThemerrDataPath = $"themerr_{new Random().Next()}.json";

var stubVideoPath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"video_stub.mp4");

// loop over each themerrDbLink
foreach (var youtubeThemeUrl in FixtureYoutubeUrls())
{
// save themerr data
var fileExists = _themerrManager.SaveThemerrData(mockThemerrDataPath, youtubeThemeUrl);
var fileExists = _themerrManager.SaveThemerrData(stubVideoPath, mockThemerrDataPath, youtubeThemeUrl);
Assert.True(fileExists, $"SaveThemerrData did not return True for {youtubeThemeUrl}");

// check if file exists
Expand All @@ -293,4 +401,27 @@ private void TestSaveThemerrData()
$"youtubeThemeUrl {youtubeThemeUrl} does not match savedYoutubeThemeUrl {savedYoutubeThemeUrl}");
}
}

[Fact]
[Trait("Category", "Unit")]
private void TestGetMd5Hash()
{
var stubVideoPath = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"video_stub.mp4");

var expectedMd5HashFile = Path.Combine(
Directory.GetCurrentDirectory(),
"data",
"video_stub.mp4.md5");

// get expected md5 hash out of file
var expectedMd5Hash = File.ReadAllText(expectedMd5HashFile).Trim();

// get actual md5 hash
var actualMd5Hash = _themerrManager.GetMd5Hash(stubVideoPath);

Assert.Equal(expectedMd5Hash, actualMd5Hash);
}
}
Binary file added Jellyfin.Plugin.Themerr.Tests/data/audio_stub.mp3
Binary file not shown.
3 changes: 3 additions & 0 deletions Jellyfin.Plugin.Themerr.Tests/data/audio_themerr_data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"theme_md5": "44c5eaa73e9b362911aaaf12a5609ab7"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"theme_md5": "abcd"
}
5 changes: 5 additions & 0 deletions Jellyfin.Plugin.Themerr.Tests/data/dummy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"downloaded_timestamp": "2023-12-12T04:07:41.7659077Z",
"youtube_theme_url": "https://www.youtube.com/watch?v=E8nxMWr2sr4",
"dummy_key": "dummy_value"
}
1 change: 1 addition & 0 deletions Jellyfin.Plugin.Themerr.Tests/data/empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Binary file not shown.
93 changes: 77 additions & 16 deletions Jellyfin.Plugin.Themerr/ThemerrManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,21 @@ public ThemerrManager(ILibraryManager libraryManager, ILogger<ThemerrManager> lo
}

/// <summary>
/// Get the existing youtube theme url from the themerr data file if it exists.
/// Get a value from the themerr data file if it exists.
/// </summary>
/// <param name="key">The key to search for.</param>
/// <param name="themerrDataPath">The path to the themerr data file.</param>
/// <returns>The existing YouTube theme url if it exists, empty string otherwise.</returns>
public static string GetExistingYoutubeThemeUrl(string themerrDataPath)
/// <returns>The value of the key if it exists, null otherwise.</returns>
public string GetExistingThemerrDataValue(string key, string themerrDataPath)
{
if (!System.IO.File.Exists(themerrDataPath))
{
return string.Empty;
return null;
}

var jsonString = System.IO.File.ReadAllText(themerrDataPath);
dynamic jsonData = JsonConvert.DeserializeObject(jsonString);
return jsonData?.youtube_theme_url;
return jsonData?[key];
}

/// <summary>
Expand Down Expand Up @@ -132,42 +133,83 @@ public void ProcessMovieTheme(Movie movie)
var themePath = GetThemePath(movie);
var themerrDataPath = GetThemerrDataPath(movie);

if (ShouldSkipDownload(themePath, themerrDataPath))
if (!ContinueDownload(themePath, themerrDataPath))
{
return;
}

var existingYoutubeThemeUrl = GetExistingYoutubeThemeUrl(themerrDataPath);
var existingYoutubeThemeUrl = GetExistingThemerrDataValue("youtube_theme_url", themerrDataPath);

// get tmdb id
var tmdbId = movie.GetProviderId(MetadataProvider.Tmdb);

// create themerrdb url
var themerrDbLink = CreateThemerrDbLink(tmdbId);
var themerrDbUrl = CreateThemerrDbLink(tmdbId);

var youtubeThemeUrl = GetYoutubeThemeUrl(themerrDbLink, movieTitle);
var youtubeThemeUrl = GetYoutubeThemeUrl(themerrDbUrl, movieTitle);

if (string.IsNullOrEmpty(youtubeThemeUrl) || youtubeThemeUrl == existingYoutubeThemeUrl)
{
return;
}

SaveMp3(themePath, youtubeThemeUrl);
SaveThemerrData(themerrDataPath, youtubeThemeUrl);
var successMp3 = SaveMp3(themePath, youtubeThemeUrl);
if (!successMp3)
{
return;
}

var successThemerrData = SaveThemerrData(themePath, themerrDataPath, youtubeThemeUrl);
if (!successThemerrData)
{
return;
}

movie.RefreshMetadata(CancellationToken.None);
}

/// <summary>
/// Check if the theme song should be downloaded.
///
/// If theme.mp3 exists and themerr.json doesn't exist, then skip to avoid overwriting user supplied themes.
/// Various checks are performed to determine if the theme song should be downloaded.
/// </summary>
/// <param name="themePath">The path to the theme song.</param>
/// <param name="themerrDataPath">The path to the themerr data file.</param>
/// <returns>True if the theme song should NOT be downloaded, false otherwise.</returns>
public bool ShouldSkipDownload(string themePath, string themerrDataPath)
/// <returns>True to continue with downloaded, false otherwise.</returns>
public bool ContinueDownload(string themePath, string themerrDataPath)
{
return System.IO.File.Exists(themePath) && !System.IO.File.Exists(themerrDataPath);
if (!System.IO.File.Exists(themePath) && !System.IO.File.Exists(themerrDataPath))
{
// neither file exists, so don't skip
return true;
}

if (!System.IO.File.Exists(themePath) && System.IO.File.Exists(themerrDataPath))
{
// the theme is missing, so delete the themerr data file
System.IO.File.Delete(themerrDataPath);
return true;
}

if (System.IO.File.Exists(themePath) && !System.IO.File.Exists(themerrDataPath))
{
// the theme is user supplied, so don't overwrite it
return false;
}

var existingThemeMd5 = GetExistingThemerrDataValue("theme_md5", themerrDataPath);

// if existing theme md5 is empty, don't skip
if (string.IsNullOrEmpty(existingThemeMd5))
{
return true;
}

// check if the theme hash matches what is in the themerr data file
var themeMd5 = GetMd5Hash(themePath);

// if hashes match, theme is supplied by themerr, otherwise it is user supplied
return themeMd5 == existingThemeMd5;
}

/// <summary>
Expand Down Expand Up @@ -226,15 +268,17 @@ public string GetYoutubeThemeUrl(string themerrDbUrl, string movieTitle)
/// <summary>
/// Save the themerr data file.
/// </summary>
/// <param name="themePath">The path to the theme song.</param>
/// <param name="themerrDataPath">The path to the themerr data file.</param>
/// <param name="youtubeThemeUrl">The YouTube theme url.</param>
/// <returns>True if the file was saved successfully, false otherwise.</returns>
public bool SaveThemerrData(string themerrDataPath, string youtubeThemeUrl)
public bool SaveThemerrData(string themePath, string themerrDataPath, string youtubeThemeUrl)
{
var success = false;
var themerrData = new
{
downloaded_timestamp = DateTime.UtcNow,
theme_md5 = GetMd5Hash(themePath),
youtube_theme_url = youtubeThemeUrl
};
try
Expand All @@ -250,6 +294,23 @@ public bool SaveThemerrData(string themerrDataPath, string youtubeThemeUrl)
return success && WaitForFile(themerrDataPath, 10000);
}

/// <summary>
/// Get the MD5 hash of a file.
/// </summary>
/// <param name="filePath">The file path.</param>
/// <returns>The MD5 hash of the file.</returns>
public string GetMd5Hash(string filePath)
{
using (var md5 = System.Security.Cryptography.MD5.Create())
{
using (var stream = System.IO.File.OpenRead(filePath))
{
var hash = md5.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", string.Empty).ToLowerInvariant();
}
}
}

/// <summary>
/// Wait for file to exist on disk and is not locked by another process.
/// </summary>
Expand Down

0 comments on commit 172e61d

Please sign in to comment.