diff --git a/EmbySDK/MediaBrowser.Common.dll b/EmbySDK/MediaBrowser.Common.dll new file mode 100644 index 0000000..73ffb41 Binary files /dev/null and b/EmbySDK/MediaBrowser.Common.dll differ diff --git a/EmbySDK/MediaBrowser.Controller.dll b/EmbySDK/MediaBrowser.Controller.dll new file mode 100644 index 0000000..2a7f050 Binary files /dev/null and b/EmbySDK/MediaBrowser.Controller.dll differ diff --git a/EmbySDK/MediaBrowser.Model.dll b/EmbySDK/MediaBrowser.Model.dll new file mode 100644 index 0000000..bab17dd Binary files /dev/null and b/EmbySDK/MediaBrowser.Model.dll differ diff --git a/NFOGuard.Emby.Plugin/Configuration/configPage.html b/NFOGuard.Emby.Plugin/Configuration/configPage.html new file mode 100644 index 0000000..25a50a7 --- /dev/null +++ b/NFOGuard.Emby.Plugin/Configuration/configPage.html @@ -0,0 +1,91 @@ + + + + NFOGuard Settings + + +
+
+
+

NFOGuard Settings

+

Configure how NFOGuard synchronizes TV episode dates in Emby.

+ +
+ +
Automatically sync PremiereDate to DateCreated when episodes are added or updated
+
+ +
+ +
Run NFOGuard as a scheduled task to process existing episodes
+
+ +
+ +
Enable detailed logging for troubleshooting
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/DEPLOYMENT.md b/NFOGuard.Emby.Plugin/DEPLOYMENT.md new file mode 100644 index 0000000..c5f2f09 --- /dev/null +++ b/NFOGuard.Emby.Plugin/DEPLOYMENT.md @@ -0,0 +1,70 @@ +# NFOGuard Emby Plugin Deployment Guide + +## Prerequisites + +- .NET Framework 4.8 SDK or Visual Studio Build Tools +- Emby Server 4.7+ running +- Access to Emby's plugins directory + +## Building the Plugin + +### Windows +```cmd +build.bat +``` + +### Linux/macOS +```bash +./build.sh +``` + +## Installation Steps + +1. **Build the plugin** using the build script above +2. **Locate your Emby plugins directory:** + - Windows: `C:\ProgramData\Emby-Server\plugins` + - Linux: `/var/lib/emby/plugins` + - Docker: `/config/plugins` (inside container) + - Synology: `/volume1/Emby/plugins` + +3. **Copy the DLL:** + ```bash + cp bin/Release/NFOGuard.Emby.Plugin.dll /path/to/emby/plugins/ + ``` + +4. **Restart Emby Server** + +5. **Verify installation:** + - Check Emby dashboard → Plugins + - Look for "NFOGuard" in the installed plugins list + - Check Emby logs for "NFOGuard :: Plugin starting up" + +## Testing + +1. **Add a new TV episode** to your library with an NFO file containing `` +2. **Check the logs** for NFOGuard processing messages: + ``` + NFOGuard :: Processing Added Episode :: Episode Name + NFOGuard :: Syncing PremiereDate to DateCreated :: Episode Name + NFOGuard :: Successfully updated :: Episode Name + ``` +3. **Verify in database:** + ```sql + sqlite3 library.db2 "SELECT DateCreated, PremiereDate FROM MediaItems WHERE Name LIKE '%episode%';" + ``` + +## Troubleshooting + +- **Plugin not appearing:** Check Emby logs for loading errors +- **Not processing episodes:** Verify log level includes Info messages +- **Build failures:** Ensure all Emby SDK DLLs are in EmbySDK folder +- **Permission errors:** Ensure Emby service has write access to database + +## SDK Dependencies + +The plugin requires these Emby SDK DLLs in the `EmbySDK` folder: +- MediaBrowser.Common.dll +- MediaBrowser.Controller.dll +- MediaBrowser.Model.dll + +These can be extracted from your Emby Server installation directory. \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/LibraryEventHandler.cs b/NFOGuard.Emby.Plugin/LibraryEventHandler.cs new file mode 100644 index 0000000..4c5f9d2 --- /dev/null +++ b/NFOGuard.Emby.Plugin/LibraryEventHandler.cs @@ -0,0 +1,153 @@ +using System; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Logging; + +namespace NFOGuard.Emby.Plugin +{ + public class LibraryEventHandler : IDisposable + { + private readonly ILibraryManager _libraryManager; + private readonly IItemRepository _itemRepository; + private readonly ILogger _logger; + private readonly LicenseManager _licenseManager; + + public LibraryEventHandler(ILibraryManager libraryManager, IItemRepository itemRepository, ILogger logger) + { + _libraryManager = libraryManager; + _itemRepository = itemRepository; + _logger = logger; + _licenseManager = new LicenseManager(_logger); + + // Subscribe to library events + _libraryManager.ItemAdded += OnItemAdded; + _libraryManager.ItemUpdated += OnItemUpdated; + } + + private void OnItemAdded(object sender, ItemChangeEventArgs e) + { + ProcessItem(e.Item, "Added"); + } + + private void OnItemUpdated(object sender, ItemChangeEventArgs e) + { + ProcessItem(e.Item, "Updated"); + } + + private void ProcessItem(BaseItem item, string eventType) + { + try + { + // Check if real-time sync is enabled + if (!Plugin.Instance.Configuration.EnableRealTimeSync) + return; + + // TODO: Re-enable licensing when ready for production + // Check license/trial status + // if (!_licenseManager.CanUseFeature()) + // { + // _logger.Info("NFOGuard :: License expired or invalid. Please purchase a license to continue using NFOGuard."); + // return; + // } + + // Process TV episodes and Movies + if (item is Episode episode) + { + ProcessEpisode(episode, eventType); + } + else if (item is Movie movie) + { + ProcessMovie(movie, eventType); + } + } + catch (Exception ex) + { + _logger.ErrorException($"NFOGuard :: Error processing item {item?.Name}", ex); + } + } + + private void ProcessEpisode(Episode episode, string eventType) + { + try + { + if (Plugin.Instance.Configuration.LogVerbose) + _logger.Info($"NFOGuard :: Processing {eventType} Episode :: {episode.Name}"); + + // Check if PremiereDate exists and differs from DateCreated + if (episode.PremiereDate.HasValue && + episode.DateCreated != episode.PremiereDate.Value) + { + if (Plugin.Instance.Configuration.LogVerbose) + _logger.Info($"NFOGuard :: Syncing PremiereDate to DateCreated :: {episode.Name} :: " + + $"PremiereDate: {episode.PremiereDate.Value}, DateCreated: {episode.DateCreated}"); + + // Update DateCreated to match PremiereDate + episode.DateCreated = episode.PremiereDate.Value; + + try + { + // Save to repository using ItemUpdateType.MetadataEdit + episode.UpdateToRepository(ItemUpdateType.MetadataEdit); + _logger.Info($"NFOGuard :: Successfully updated episode :: {episode.Name}"); + } + catch (Exception ex) + { + _logger.ErrorException($"NFOGuard :: Failed to update episode {episode.Name}", ex); + } + } + } + catch (Exception ex) + { + _logger.ErrorException($"NFOGuard :: Error processing episode {episode?.Name}", ex); + } + } + + private void ProcessMovie(Movie movie, string eventType) + { + try + { + if (Plugin.Instance.Configuration.LogVerbose) + _logger.Info($"NFOGuard :: Processing {eventType} Movie :: {movie.Name}"); + + // Check if PremiereDate exists and differs from DateCreated + if (movie.PremiereDate.HasValue && + movie.DateCreated != movie.PremiereDate.Value) + { + if (Plugin.Instance.Configuration.LogVerbose) + _logger.Info($"NFOGuard :: Syncing PremiereDate to DateCreated :: {movie.Name} :: " + + $"PremiereDate: {movie.PremiereDate.Value}, DateCreated: {movie.DateCreated}"); + + // Update DateCreated to match PremiereDate + movie.DateCreated = movie.PremiereDate.Value; + + try + { + // Save to repository using ItemUpdateType.MetadataEdit + movie.UpdateToRepository(ItemUpdateType.MetadataEdit); + _logger.Info($"NFOGuard :: Successfully updated movie :: {movie.Name}"); + } + catch (Exception ex) + { + _logger.ErrorException($"NFOGuard :: Failed to update movie {movie.Name}", ex); + } + } + } + catch (Exception ex) + { + _logger.ErrorException($"NFOGuard :: Error processing movie {movie?.Name}", ex); + } + } + + public void Dispose() + { + if (_libraryManager != null) + { + _libraryManager.ItemAdded -= OnItemAdded; + _libraryManager.ItemUpdated -= OnItemUpdated; + } + } + } +} \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/LicenseManager.cs b/NFOGuard.Emby.Plugin/LicenseManager.cs new file mode 100644 index 0000000..bbe6adb --- /dev/null +++ b/NFOGuard.Emby.Plugin/LicenseManager.cs @@ -0,0 +1,66 @@ +using System; +using System.Threading.Tasks; +using MediaBrowser.Model.Logging; + +namespace NFOGuard.Emby.Plugin +{ + public class LicenseManager + { + private readonly ILogger _logger; + + public LicenseManager(ILogger logger) + { + _logger = logger; + } + + public async Task ValidateLicenseAsync(string licenseKey) + { + try + { + // TODO: Implement actual license validation + // For now, return true for testing + // In production, this would call your licensing server + + if (string.IsNullOrEmpty(licenseKey)) + { + _logger.Info("NFOGuard :: No license key provided, running in trial mode"); + return false; + } + + // Placeholder for license validation logic + // You would implement your own licensing server/API here + _logger.Info($"NFOGuard :: Validating license key: {licenseKey.Substring(0, Math.Min(8, licenseKey.Length))}..."); + + // For now, accept any non-empty key as valid + return !string.IsNullOrWhiteSpace(licenseKey); + } + catch (Exception ex) + { + _logger.ErrorException("NFOGuard :: License validation error", ex); + return false; + } + } + + public bool IsTrialExpired() + { + // Implement trial logic here + // For example, check installation date vs current date + var installDate = Plugin.Instance.Configuration.InstallDate; + if (installDate == DateTime.MinValue) + { + Plugin.Instance.Configuration.InstallDate = DateTime.UtcNow; + Plugin.Instance.SaveConfiguration(); + return false; + } + + var trialDays = 30; // 30-day trial + return DateTime.UtcNow > installDate.AddDays(trialDays); + } + + public bool CanUseFeature() + { + // Check if license is valid or trial is still active + return Plugin.Instance.Configuration.LicenseValid || !IsTrialExpired(); + } + } +} \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj b/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj new file mode 100644 index 0000000..3ff8482 --- /dev/null +++ b/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj @@ -0,0 +1,35 @@ + + + + net48 + Library + NFOGuard.Emby.Plugin + NFOGuard.Emby.Plugin + false + + + + + + + + + ..\EmbySDK\MediaBrowser.Common.dll + false + + + ..\EmbySDK\MediaBrowser.Controller.dll + false + + + ..\EmbySDK\MediaBrowser.Model.dll + false + + + + + + + + + \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/NFOGuardScheduledTask.cs b/NFOGuard.Emby.Plugin/NFOGuardScheduledTask.cs new file mode 100644 index 0000000..c288409 --- /dev/null +++ b/NFOGuard.Emby.Plugin/NFOGuardScheduledTask.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Tasks; + +namespace NFOGuard.Emby.Plugin +{ + public class NFOGuardScheduledTask : IScheduledTask + { + private readonly ILibraryManager _libraryManager; + private readonly ILogger _logger; + + public string Name => "NFOGuard Date Sync"; + + public string Key => "NFOGuardSync"; + + public string Description => "Synchronizes PremiereDate to DateCreated for all TV episodes and movies"; + + public string Category => "Library"; + + public NFOGuardScheduledTask(ILibraryManager libraryManager, ILogManager logManager) + { + _libraryManager = libraryManager; + _logger = logManager.GetLogger("NFOGuard"); + } + + public async Task Execute(CancellationToken cancellationToken, IProgress progress) + { + if (!Plugin.Instance.Configuration.EnableScheduledTask) + { + _logger.Info("NFOGuard :: Scheduled task is disabled in configuration"); + return; + } + + _logger.Info("NFOGuard :: Starting scheduled task execution"); + + try + { + // Get all TV episodes and movies + var episodes = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { "Episode" }, + Recursive = true + }).OfType().ToList(); + + var movies = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { "Movie" }, + Recursive = true + }).OfType().ToList(); + + var totalItems = episodes.Count + movies.Count; + _logger.Info($"NFOGuard :: Found {episodes.Count} episodes and {movies.Count} movies to process ({totalItems} total)"); + + var processedCount = 0; + var updatedCount = 0; + + // Process episodes + for (int i = 0; i < episodes.Count; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + var episode = episodes[i]; + try + { + if (episode.PremiereDate.HasValue && + episode.DateCreated != episode.PremiereDate.Value) + { + if (Plugin.Instance.Configuration.LogVerbose) + _logger.Info($"NFOGuard :: Syncing episode: {episode.Name} :: " + + $"PremiereDate: {episode.PremiereDate.Value}, DateCreated: {episode.DateCreated}"); + + episode.DateCreated = episode.PremiereDate.Value; + episode.UpdateToRepository(ItemUpdateType.MetadataEdit); + updatedCount++; + } + + processedCount++; + var progressPercentage = (double)processedCount / totalItems * 100; + progress?.Report(progressPercentage); + + } + catch (Exception ex) + { + _logger.ErrorException($"NFOGuard :: Error processing episode {episode.Name}", ex); + } + } + + // Process movies + for (int i = 0; i < movies.Count; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + var movie = movies[i]; + try + { + if (movie.PremiereDate.HasValue && + movie.DateCreated != movie.PremiereDate.Value) + { + if (Plugin.Instance.Configuration.LogVerbose) + _logger.Info($"NFOGuard :: Syncing movie: {movie.Name} :: " + + $"PremiereDate: {movie.PremiereDate.Value}, DateCreated: {movie.DateCreated}"); + + movie.DateCreated = movie.PremiereDate.Value; + movie.UpdateToRepository(ItemUpdateType.MetadataEdit); + updatedCount++; + } + + processedCount++; + var progressPercentage = (double)processedCount / totalItems * 100; + progress?.Report(progressPercentage); + + } + catch (Exception ex) + { + _logger.ErrorException($"NFOGuard :: Error processing movie {movie.Name}", ex); + } + } + + _logger.Info($"NFOGuard :: Scheduled task completed. Processed: {processedCount} items ({episodes.Count} episodes, {movies.Count} movies), Updated: {updatedCount}"); + } + catch (Exception ex) + { + _logger.ErrorException("NFOGuard :: Scheduled task failed", ex); + throw; + } + } + + public IEnumerable GetDefaultTriggers() + { + // Default to run weekly at 3 AM on Sunday + return new[] + { + new TaskTriggerInfo + { + Type = TaskTriggerInfo.TriggerWeekly, + DayOfWeek = DayOfWeek.Sunday, + TimeOfDayTicks = TimeSpan.FromHours(3).Ticks + } + }; + } + } +} \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/Plugin.cs b/NFOGuard.Emby.Plugin/Plugin.cs new file mode 100644 index 0000000..9e9ff4b --- /dev/null +++ b/NFOGuard.Emby.Plugin/Plugin.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace NFOGuard.Emby.Plugin +{ + public class Plugin : BasePlugin, IHasThumbImage + { + private Guid _id = new Guid("A1B2C3D4-5678-9ABC-DEF0-123456789ABC"); + + public override string Name => "NFOGuard"; + + public override string Description => "Synchronizes TV episode PremiereDate to DateCreated to preserve import dates in Emby"; + + public override Guid Id => _id; + + public override string ConfigurationFileName => "nfoguard.xml"; + + public ImageFormat ThumbImageFormat => ImageFormat.Png; + + public static Plugin Instance { get; private set; } + + public Stream GetThumbImage() + { + var type = GetType(); + return type.Assembly.GetManifestResourceStream(type.Namespace + ".thumb.png"); + } + + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + } + + public class PluginConfiguration : BasePluginConfiguration + { + public bool EnableRealTimeSync { get; set; } = true; + public bool EnableScheduledTask { get; set; } = true; + public bool LogVerbose { get; set; } = false; + public bool LicenseValid { get; set; } = false; + public string LicenseKey { get; set; } = string.Empty; + public DateTime InstallDate { get; set; } = DateTime.MinValue; + } +} \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/PluginConfigurationPage.cs.disabled b/NFOGuard.Emby.Plugin/PluginConfigurationPage.cs.disabled new file mode 100644 index 0000000..61713ca --- /dev/null +++ b/NFOGuard.Emby.Plugin/PluginConfigurationPage.cs.disabled @@ -0,0 +1,29 @@ +using System.IO; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.Plugins; + +namespace NFOGuard.Emby.Plugin +{ + public class PluginConfigurationPage : IPluginConfigurationPage + { + public string Name => "NFOGuard"; + + public ConfigurationPageType ConfigurationPageType => ConfigurationPageType.PluginConfiguration; + + public IPlugin Plugin => NFOGuard.Emby.Plugin.Plugin.Instance; + + public Stream GetHtmlStream() + { + var type = GetType(); + var stream = type.Assembly.GetManifestResourceStream("NFOGuard.Emby.Plugin.Configuration.configPage.html"); + + if (stream == null) + { + // Try alternative resource names + stream = type.Assembly.GetManifestResourceStream("NFOGuard.Emby.Plugin.configPage.html"); + } + + return stream; + } + } +} \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/PluginEntryPoint.cs b/NFOGuard.Emby.Plugin/PluginEntryPoint.cs new file mode 100644 index 0000000..d70ea35 --- /dev/null +++ b/NFOGuard.Emby.Plugin/PluginEntryPoint.cs @@ -0,0 +1,44 @@ +using System; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Logging; + +namespace NFOGuard.Emby.Plugin +{ + public class PluginEntryPoint : IServerEntryPoint + { + private readonly ILibraryManager _libraryManager; + private readonly IItemRepository _itemRepository; + private readonly ILogger _logger; + private LibraryEventHandler _eventHandler; + + public PluginEntryPoint(ILibraryManager libraryManager, IItemRepository itemRepository, ILogManager logManager) + { + _libraryManager = libraryManager; + _itemRepository = itemRepository; + _logger = logManager.GetLogger("NFOGuard"); + } + + public void Run() + { + _logger.Info("NFOGuard :: Plugin starting up"); + + try + { + _eventHandler = new LibraryEventHandler(_libraryManager, _itemRepository, _logger); + _logger.Info("NFOGuard :: Event handlers registered successfully"); + } + catch (Exception ex) + { + _logger.ErrorException("NFOGuard :: Failed to initialize event handlers", ex); + } + } + + public void Dispose() + { + _logger.Info("NFOGuard :: Plugin shutting down"); + _eventHandler?.Dispose(); + } + } +} \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/Properties/AssemblyInfo.cs b/NFOGuard.Emby.Plugin/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..03c496c --- /dev/null +++ b/NFOGuard.Emby.Plugin/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NFOGuard Emby Plugin")] +[assembly: AssemblyDescription("Synchronizes TV episode PremiereDate to DateCreated to preserve import dates in Emby")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("NFOGuard")] +[assembly: AssemblyProduct("NFOGuard.Emby.Plugin")] +[assembly: AssemblyCopyright("Copyright © NFOGuard 2025")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b8a7f9e2-1234-4567-8901-2b3c4d5e6f7a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/README.md b/NFOGuard.Emby.Plugin/README.md new file mode 100644 index 0000000..da84a8d --- /dev/null +++ b/NFOGuard.Emby.Plugin/README.md @@ -0,0 +1,28 @@ +# NFOGuard Emby Plugin + +This is the companion Emby plugin for NFOGuard that automatically synchronizes TV episode PremiereDate to DateCreated to preserve import dates. + +## Features + +- Real-time processing of new and updated TV episodes +- Automatically syncs PremiereDate → DateCreated when episodes are added/scanned +- Preserves original import dates from NFO files +- Logging for debugging and monitoring + +## Installation + +1. Build the plugin DLL +2. Copy to Emby's plugins directory +3. Restart Emby server +4. Plugin will automatically start processing episodes + +## How It Works + +The plugin hooks into Emby's library events (ItemAdded/ItemUpdated) and: + +1. Detects when TV episodes are processed +2. Checks if PremiereDate exists and differs from DateCreated +3. Updates DateCreated to match PremiereDate +4. Saves changes to the Emby database + +This ensures that Emby displays the correct import date (from NFO dateadded) instead of the scan date. \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/WSL_SETUP.md b/NFOGuard.Emby.Plugin/WSL_SETUP.md new file mode 100644 index 0000000..2a80a01 --- /dev/null +++ b/NFOGuard.Emby.Plugin/WSL_SETUP.md @@ -0,0 +1,131 @@ +# NFOGuard Emby Plugin - WSL Development Setup + +This guide helps you build the NFOGuard Emby plugin on Windows using WSL (Windows Subsystem for Linux). + +## Prerequisites + +### 1. Install .NET SDK in WSL +```bash +# Update package list +sudo apt update + +# Install .NET SDK 6.0 (supports .NET Framework 4.8 development) +sudo apt install -y dotnet-sdk-6.0 + +# Verify installation +dotnet --version +``` + +### 2. Get Emby SDK Files +You need to obtain the Emby SDK DLLs from an Emby Server installation: + +**Option A: From existing Emby Server** +1. Locate your Emby Server installation directory: + - Windows: `C:\Program Files\Emby-Server\System` + - Or wherever you installed Emby Server + +2. Copy these files to `NFOguard/EmbySDK/`: + - `MediaBrowser.Common.dll` + - `MediaBrowser.Controller.dll` + - `MediaBrowser.Model.dll` + +**Option B: Download from official sources** +- Check the [Emby Plugin Development docs](https://dev.emby.media/doc/plugins/dev/index.html) + +### 3. Prepare EmbySDK Directory +```bash +# From the NFOguard directory +mkdir -p EmbySDK + +# Copy the DLL files here (use Windows Explorer or copy commands) +# You should have: +# EmbySDK/MediaBrowser.Common.dll +# EmbySDK/MediaBrowser.Controller.dll +# EmbySDK/MediaBrowser.Model.dll +``` + +## Building the Plugin + +### Option 1: Using Build Script (Recommended) +```bash +cd NFOGuard.Emby.Plugin +./build.sh +``` + +### Option 2: Manual Build +```bash +cd NFOGuard.Emby.Plugin + +# Clean previous builds +rm -rf bin obj + +# Build the project +dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48 +``` + +## Troubleshooting + +### Common Issues + +**1. "MediaBrowser.* not found" errors:** +- Ensure SDK DLLs are in `../EmbySDK/` relative to the .csproj file +- Check DLL file names match exactly (case-sensitive on Linux) + +**2. ".NET Framework 4.8 targeting pack" errors:** +```bash +# Install Mono for .NET Framework support +sudo apt install mono-complete +``` + +**3. Permission errors:** +```bash +# Make build script executable +chmod +x build.sh +``` + +**4. WSL file system issues:** +- Make sure you're working in the Linux filesystem (`/home/...`) not Windows mounts (`/mnt/c/...`) for better performance +- If files are on Windows side, they may have permission/line ending issues + +### Verifying Build Success + +After successful build: +```bash +# Check if DLL was created +ls -la bin/Release/net48/NFOGuard.Emby.Plugin.dll + +# Check file size (should be > 0 bytes) +file bin/Release/net48/NFOGuard.Emby.Plugin.dll +``` + +## Deployment to Windows Emby Server + +1. **Locate Emby plugins directory:** + - `C:\ProgramData\Emby-Server\plugins` + +2. **Copy from WSL to Windows:** + ```bash + # From WSL, copy to Windows (adjust path as needed) + cp bin/Release/net48/NFOGuard.Emby.Plugin.dll /mnt/c/ProgramData/Emby-Server/plugins/ + ``` + +3. **Restart Emby Server** + +4. **Verify in Emby Dashboard:** + - Go to Dashboard → Plugins + - Look for "NFOGuard" in installed plugins + +## Development Tips + +- Use VS Code in WSL for editing: `code .` +- Keep source files in WSL filesystem for best performance +- Only copy final DLL to Windows Emby server +- Use `tail -f /var/log/emby.log` (or wherever Emby logs are) to monitor plugin activity + +## Next Steps + +Once built and deployed: +1. Test with a new TV episode import +2. Check Emby logs for NFOGuard messages +3. Verify DateCreated syncs to PremiereDate in database +4. Configure licensing when ready for production use \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/bin/Release/net48/NFOGuard.Emby.Plugin.dll b/NFOGuard.Emby.Plugin/bin/Release/net48/NFOGuard.Emby.Plugin.dll new file mode 100644 index 0000000..2b50630 Binary files /dev/null and b/NFOGuard.Emby.Plugin/bin/Release/net48/NFOGuard.Emby.Plugin.dll differ diff --git a/NFOGuard.Emby.Plugin/bin/Release/net48/NFOGuard.Emby.Plugin.pdb b/NFOGuard.Emby.Plugin/bin/Release/net48/NFOGuard.Emby.Plugin.pdb new file mode 100644 index 0000000..810d2cd Binary files /dev/null and b/NFOGuard.Emby.Plugin/bin/Release/net48/NFOGuard.Emby.Plugin.pdb differ diff --git a/NFOGuard.Emby.Plugin/bin/Release/net48/System.Buffers.dll b/NFOGuard.Emby.Plugin/bin/Release/net48/System.Buffers.dll new file mode 100755 index 0000000..f2d83c5 Binary files /dev/null and b/NFOGuard.Emby.Plugin/bin/Release/net48/System.Buffers.dll differ diff --git a/NFOGuard.Emby.Plugin/bin/Release/net48/System.Memory.dll b/NFOGuard.Emby.Plugin/bin/Release/net48/System.Memory.dll new file mode 100755 index 0000000..4617199 Binary files /dev/null and b/NFOGuard.Emby.Plugin/bin/Release/net48/System.Memory.dll differ diff --git a/NFOGuard.Emby.Plugin/bin/Release/net48/System.Numerics.Vectors.dll b/NFOGuard.Emby.Plugin/bin/Release/net48/System.Numerics.Vectors.dll new file mode 100755 index 0000000..0865972 Binary files /dev/null and b/NFOGuard.Emby.Plugin/bin/Release/net48/System.Numerics.Vectors.dll differ diff --git a/NFOGuard.Emby.Plugin/bin/Release/net48/System.Runtime.CompilerServices.Unsafe.dll b/NFOGuard.Emby.Plugin/bin/Release/net48/System.Runtime.CompilerServices.Unsafe.dll new file mode 100755 index 0000000..de9e124 Binary files /dev/null and b/NFOGuard.Emby.Plugin/bin/Release/net48/System.Runtime.CompilerServices.Unsafe.dll differ diff --git a/NFOGuard.Emby.Plugin/build.bat b/NFOGuard.Emby.Plugin/build.bat new file mode 100644 index 0000000..ea5e853 --- /dev/null +++ b/NFOGuard.Emby.Plugin/build.bat @@ -0,0 +1,41 @@ +@echo off +echo Building NFOGuard Emby Plugin... + +REM Check if MSBuild is available +where msbuild >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo ERROR: MSBuild not found. Please install Visual Studio Build Tools or use Developer Command Prompt. + pause + exit /b 1 +) + +REM Clean previous builds +if exist "bin" rmdir /s /q "bin" +if exist "obj" rmdir /s /q "obj" + +REM Build the project +msbuild NFOGuard.Emby.Plugin.csproj /p:Configuration=Release /p:Platform="Any CPU" + +if %ERRORLEVEL% equ 0 ( + echo. + echo Build successful! + echo Plugin DLL location: bin\Release\net48\NFOGuard.Emby.Plugin.dll + echo. + + REM Create plugin-release directory in repo root and copy DLL + if not exist "..\plugin-release" mkdir "..\plugin-release" + copy "bin\Release\net48\NFOGuard.Emby.Plugin.dll" "..\plugin-release\" + + echo Plugin copied to: ..\plugin-release\NFOGuard.Emby.Plugin.dll + echo. + echo To install: + echo 1. Copy plugin-release\NFOGuard.Emby.Plugin.dll to your Emby plugins folder + echo 2. Restart Emby Server + echo. +) else ( + echo. + echo Build failed! Check the output above for errors. + echo. +) + +pause \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/build.sh b/NFOGuard.Emby.Plugin/build.sh new file mode 100755 index 0000000..fd44136 --- /dev/null +++ b/NFOGuard.Emby.Plugin/build.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +echo "Building NFOGuard Emby Plugin..." + +# Check if we need to install .NET Framework support +if ! command -v dotnet &> /dev/null; then + echo "ERROR: .NET SDK not found. Installing..." + echo "Run: sudo apt update && sudo apt install -y dotnet-sdk-6.0" + exit 1 +fi + +# Create EmbySDK directory if it doesn't exist +if [ ! -d "../EmbySDK" ]; then + echo "Creating EmbySDK directory..." + mkdir -p ../EmbySDK + echo "" + echo "IMPORTANT: You need to copy Emby SDK DLLs to ../EmbySDK/" + echo "Required files:" + echo " - MediaBrowser.Common.dll" + echo " - MediaBrowser.Controller.dll" + echo " - MediaBrowser.Model.dll" + echo "" + echo "These can be found in your Emby Server installation directory." + echo "Continue? (y/n)" + read -r answer + if [[ $answer != "y" ]]; then + echo "Build cancelled. Please copy SDK files first." + exit 1 + fi +fi + +# Clean previous builds +rm -rf bin obj + +# Try building with dotnet first (requires SDK), fall back to msbuild +if command -v dotnet &> /dev/null; then + echo "Building with .NET CLI..." + dotnet build NFOGuard.Emby.Plugin.csproj -c Release -f net48 +elif command -v msbuild &> /dev/null; then + echo "Building with MSBuild..." + msbuild NFOGuard.Emby.Plugin.csproj /p:Configuration=Release /p:Platform="Any CPU" +else + echo "ERROR: No build tool found. Install .NET SDK or Mono with MSBuild." + exit 1 +fi + +if [ $? -eq 0 ]; then + echo + echo "Build successful!" + echo "Plugin DLL location: bin/Release/net48/NFOGuard.Emby.Plugin.dll" + echo + + # Create plugin-release directory in repo root and copy DLL + PLUGIN_RELEASE_DIR="../plugin-release" + mkdir -p "$PLUGIN_RELEASE_DIR" + cp "bin/Release/net48/NFOGuard.Emby.Plugin.dll" "$PLUGIN_RELEASE_DIR/" + + echo "Plugin copied to: $PLUGIN_RELEASE_DIR/NFOGuard.Emby.Plugin.dll" + echo + echo "To install:" + echo "1. Copy plugin-release/NFOGuard.Emby.Plugin.dll to your Emby plugins folder" + echo "2. Restart Emby Server" + echo +else + echo + echo "Build failed! Check the output above for errors." + echo +fi \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/obj/NFOGuard.Emby.Plugin.csproj.nuget.dgspec.json b/NFOGuard.Emby.Plugin/obj/NFOGuard.Emby.Plugin.csproj.nuget.dgspec.json new file mode 100644 index 0000000..d51a862 --- /dev/null +++ b/NFOGuard.Emby.Plugin/obj/NFOGuard.Emby.Plugin.csproj.nuget.dgspec.json @@ -0,0 +1,62 @@ +{ + "format": 1, + "restore": { + "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj": {} + }, + "projects": { + "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj", + "projectName": "NFOGuard.Emby.Plugin", + "projectPath": "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj", + "packagesPath": "/home/jskala/.nuget/packages/", + "outputPath": "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/", + "projectStyle": "PackageReference", + "configFilePaths": [ + "/home/jskala/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net48" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net48": { + "targetAlias": "net48", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + } + }, + "frameworks": { + "net48": { + "targetAlias": "net48", + "dependencies": { + "Microsoft.NETFramework.ReferenceAssemblies": { + "suppressParent": "All", + "target": "Package", + "version": "[1.0.3, )", + "autoReferenced": true + }, + "System.Memory": { + "target": "Package", + "version": "[4.5.5, )" + } + }, + "runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/8.0.414/RuntimeIdentifierGraph.json" + } + } + } + } +} \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/obj/NFOGuard.Emby.Plugin.csproj.nuget.g.props b/NFOGuard.Emby.Plugin/obj/NFOGuard.Emby.Plugin.csproj.nuget.g.props new file mode 100644 index 0000000..0dcf946 --- /dev/null +++ b/NFOGuard.Emby.Plugin/obj/NFOGuard.Emby.Plugin.csproj.nuget.g.props @@ -0,0 +1,15 @@ + + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + /home/jskala/.nuget/packages/ + /home/jskala/.nuget/packages/ + PackageReference + 6.11.1 + + + + + \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/obj/NFOGuard.Emby.Plugin.csproj.nuget.g.targets b/NFOGuard.Emby.Plugin/obj/NFOGuard.Emby.Plugin.csproj.nuget.g.targets new file mode 100644 index 0000000..7f5172a --- /dev/null +++ b/NFOGuard.Emby.Plugin/obj/NFOGuard.Emby.Plugin.csproj.nuget.g.targets @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/obj/Release/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs b/NFOGuard.Emby.Plugin/obj/Release/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs new file mode 100644 index 0000000..9da2331 --- /dev/null +++ b/NFOGuard.Emby.Plugin/obj/Release/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] diff --git a/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.GeneratedMSBuildEditorConfig.editorconfig b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..07e3496 --- /dev/null +++ b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,5 @@ +is_global = true +build_property.RootNamespace = NFOGuard.Emby.Plugin +build_property.ProjectDir = /home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = diff --git a/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.assets.cache b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.assets.cache new file mode 100644 index 0000000..f3a616c Binary files /dev/null and b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.assets.cache differ diff --git a/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.AssemblyReference.cache b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.AssemblyReference.cache new file mode 100644 index 0000000..8c05756 Binary files /dev/null and b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.AssemblyReference.cache differ diff --git a/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.CoreCompileInputs.cache b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..2571ca1 --- /dev/null +++ b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +bc9346eb5351b08e69d51772894aaede4c86d42a30ba9df1c921e5156198813e diff --git a/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.FileListAbsolute.txt b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..ab3e3e7 --- /dev/null +++ b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.FileListAbsolute.txt @@ -0,0 +1,12 @@ +/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/NFOGuard.Emby.Plugin.dll +/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/NFOGuard.Emby.Plugin.pdb +/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/System.Buffers.dll +/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/System.Memory.dll +/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/System.Numerics.Vectors.dll +/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/bin/Release/net48/System.Runtime.CompilerServices.Unsafe.dll +/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.AssemblyReference.cache +/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.GeneratedMSBuildEditorConfig.editorconfig +/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.csproj.CoreCompileInputs.cache +/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.F23561FE.Up2Date +/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.dll +/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.pdb diff --git a/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.dll b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.dll new file mode 100644 index 0000000..2b50630 Binary files /dev/null and b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.dll differ diff --git a/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.pdb b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.pdb new file mode 100644 index 0000000..810d2cd Binary files /dev/null and b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.Emby.Plugin.pdb differ diff --git a/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.F23561FE.Up2Date b/NFOGuard.Emby.Plugin/obj/Release/net48/NFOGuard.F23561FE.Up2Date new file mode 100644 index 0000000..e69de29 diff --git a/NFOGuard.Emby.Plugin/obj/project.assets.json b/NFOGuard.Emby.Plugin/obj/project.assets.json new file mode 100644 index 0000000..a697a0f --- /dev/null +++ b/NFOGuard.Emby.Plugin/obj/project.assets.json @@ -0,0 +1,676 @@ +{ + "version": 3, + "targets": { + ".NETFramework,Version=v4.8": { + "Microsoft.NETFramework.ReferenceAssemblies/1.0.3": { + "type": "package", + "dependencies": { + "Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3" + } + }, + "Microsoft.NETFramework.ReferenceAssemblies.net48/1.0.3": { + "type": "package", + "build": { + "build/Microsoft.NETFramework.ReferenceAssemblies.net48.targets": {} + } + }, + "System.Buffers/4.5.1": { + "type": "package", + "frameworkAssemblies": [ + "mscorlib" + ], + "compile": { + "ref/net45/System.Buffers.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net461/System.Buffers.dll": { + "related": ".xml" + } + } + }, + "System.Memory/4.5.5": { + "type": "package", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.5.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + }, + "frameworkAssemblies": [ + "System", + "mscorlib" + ], + "compile": { + "lib/net461/System.Memory.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net461/System.Memory.dll": { + "related": ".xml" + } + } + }, + "System.Numerics.Vectors/4.5.0": { + "type": "package", + "frameworkAssemblies": [ + "System.Numerics", + "mscorlib" + ], + "compile": { + "ref/net46/System.Numerics.Vectors.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net46/System.Numerics.Vectors.dll": { + "related": ".xml" + } + } + }, + "System.Runtime.CompilerServices.Unsafe/4.5.3": { + "type": "package", + "frameworkAssemblies": [ + "mscorlib" + ], + "compile": { + "ref/net461/System.Runtime.CompilerServices.Unsafe.dll": { + "related": ".xml" + } + }, + "runtime": { + "lib/net461/System.Runtime.CompilerServices.Unsafe.dll": { + "related": ".xml" + } + } + } + } + }, + "libraries": { + "Microsoft.NETFramework.ReferenceAssemblies/1.0.3": { + "sha512": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", + "type": "package", + "path": "microsoft.netframework.referenceassemblies/1.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "microsoft.netframework.referenceassemblies.1.0.3.nupkg.sha512", + "microsoft.netframework.referenceassemblies.nuspec" + ] + }, + "Microsoft.NETFramework.ReferenceAssemblies.net48/1.0.3": { + "sha512": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ==", + "type": "package", + "path": "microsoft.netframework.referenceassemblies.net48/1.0.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "build/.NETFramework/v4.8/Accessibility.dll", + "build/.NETFramework/v4.8/Accessibility.xml", + "build/.NETFramework/v4.8/CustomMarshalers.dll", + "build/.NETFramework/v4.8/CustomMarshalers.xml", + "build/.NETFramework/v4.8/Facades/Microsoft.Win32.Primitives.dll", + "build/.NETFramework/v4.8/Facades/System.AppContext.dll", + "build/.NETFramework/v4.8/Facades/System.Collections.Concurrent.dll", + "build/.NETFramework/v4.8/Facades/System.Collections.NonGeneric.dll", + "build/.NETFramework/v4.8/Facades/System.Collections.Specialized.dll", + "build/.NETFramework/v4.8/Facades/System.Collections.dll", + "build/.NETFramework/v4.8/Facades/System.ComponentModel.Annotations.dll", + "build/.NETFramework/v4.8/Facades/System.ComponentModel.EventBasedAsync.dll", + "build/.NETFramework/v4.8/Facades/System.ComponentModel.Primitives.dll", + "build/.NETFramework/v4.8/Facades/System.ComponentModel.TypeConverter.dll", + "build/.NETFramework/v4.8/Facades/System.ComponentModel.dll", + "build/.NETFramework/v4.8/Facades/System.Console.dll", + "build/.NETFramework/v4.8/Facades/System.Data.Common.dll", + "build/.NETFramework/v4.8/Facades/System.Diagnostics.Contracts.dll", + "build/.NETFramework/v4.8/Facades/System.Diagnostics.Debug.dll", + "build/.NETFramework/v4.8/Facades/System.Diagnostics.FileVersionInfo.dll", + "build/.NETFramework/v4.8/Facades/System.Diagnostics.Process.dll", + "build/.NETFramework/v4.8/Facades/System.Diagnostics.StackTrace.dll", + "build/.NETFramework/v4.8/Facades/System.Diagnostics.TextWriterTraceListener.dll", + "build/.NETFramework/v4.8/Facades/System.Diagnostics.Tools.dll", + "build/.NETFramework/v4.8/Facades/System.Diagnostics.TraceSource.dll", + "build/.NETFramework/v4.8/Facades/System.Drawing.Primitives.dll", + "build/.NETFramework/v4.8/Facades/System.Dynamic.Runtime.dll", + "build/.NETFramework/v4.8/Facades/System.Globalization.Calendars.dll", + "build/.NETFramework/v4.8/Facades/System.Globalization.Extensions.dll", + "build/.NETFramework/v4.8/Facades/System.Globalization.dll", + "build/.NETFramework/v4.8/Facades/System.IO.Compression.ZipFile.dll", + "build/.NETFramework/v4.8/Facades/System.IO.FileSystem.DriveInfo.dll", + "build/.NETFramework/v4.8/Facades/System.IO.FileSystem.Primitives.dll", + "build/.NETFramework/v4.8/Facades/System.IO.FileSystem.Watcher.dll", + "build/.NETFramework/v4.8/Facades/System.IO.FileSystem.dll", + "build/.NETFramework/v4.8/Facades/System.IO.IsolatedStorage.dll", + "build/.NETFramework/v4.8/Facades/System.IO.MemoryMappedFiles.dll", + "build/.NETFramework/v4.8/Facades/System.IO.Pipes.dll", + "build/.NETFramework/v4.8/Facades/System.IO.UnmanagedMemoryStream.dll", + "build/.NETFramework/v4.8/Facades/System.IO.dll", + "build/.NETFramework/v4.8/Facades/System.Linq.Expressions.dll", + "build/.NETFramework/v4.8/Facades/System.Linq.Parallel.dll", + "build/.NETFramework/v4.8/Facades/System.Linq.Queryable.dll", + "build/.NETFramework/v4.8/Facades/System.Linq.dll", + "build/.NETFramework/v4.8/Facades/System.Net.Http.Rtc.dll", + "build/.NETFramework/v4.8/Facades/System.Net.NameResolution.dll", + "build/.NETFramework/v4.8/Facades/System.Net.NetworkInformation.dll", + "build/.NETFramework/v4.8/Facades/System.Net.Ping.dll", + "build/.NETFramework/v4.8/Facades/System.Net.Primitives.dll", + "build/.NETFramework/v4.8/Facades/System.Net.Requests.dll", + "build/.NETFramework/v4.8/Facades/System.Net.Security.dll", + "build/.NETFramework/v4.8/Facades/System.Net.Sockets.dll", + "build/.NETFramework/v4.8/Facades/System.Net.WebHeaderCollection.dll", + "build/.NETFramework/v4.8/Facades/System.Net.WebSockets.Client.dll", + "build/.NETFramework/v4.8/Facades/System.Net.WebSockets.dll", + "build/.NETFramework/v4.8/Facades/System.ObjectModel.dll", + "build/.NETFramework/v4.8/Facades/System.Reflection.Emit.ILGeneration.dll", + "build/.NETFramework/v4.8/Facades/System.Reflection.Emit.Lightweight.dll", + "build/.NETFramework/v4.8/Facades/System.Reflection.Emit.dll", + "build/.NETFramework/v4.8/Facades/System.Reflection.Extensions.dll", + "build/.NETFramework/v4.8/Facades/System.Reflection.Primitives.dll", + "build/.NETFramework/v4.8/Facades/System.Reflection.dll", + "build/.NETFramework/v4.8/Facades/System.Resources.Reader.dll", + "build/.NETFramework/v4.8/Facades/System.Resources.ResourceManager.dll", + "build/.NETFramework/v4.8/Facades/System.Resources.Writer.dll", + "build/.NETFramework/v4.8/Facades/System.Runtime.CompilerServices.VisualC.dll", + "build/.NETFramework/v4.8/Facades/System.Runtime.Extensions.dll", + "build/.NETFramework/v4.8/Facades/System.Runtime.Handles.dll", + "build/.NETFramework/v4.8/Facades/System.Runtime.InteropServices.RuntimeInformation.dll", + "build/.NETFramework/v4.8/Facades/System.Runtime.InteropServices.WindowsRuntime.dll", + "build/.NETFramework/v4.8/Facades/System.Runtime.InteropServices.dll", + "build/.NETFramework/v4.8/Facades/System.Runtime.Numerics.dll", + "build/.NETFramework/v4.8/Facades/System.Runtime.Serialization.Formatters.dll", + "build/.NETFramework/v4.8/Facades/System.Runtime.Serialization.Json.dll", + "build/.NETFramework/v4.8/Facades/System.Runtime.Serialization.Primitives.dll", + "build/.NETFramework/v4.8/Facades/System.Runtime.Serialization.Xml.dll", + "build/.NETFramework/v4.8/Facades/System.Runtime.dll", + "build/.NETFramework/v4.8/Facades/System.Security.Claims.dll", + "build/.NETFramework/v4.8/Facades/System.Security.Cryptography.Algorithms.dll", + "build/.NETFramework/v4.8/Facades/System.Security.Cryptography.Csp.dll", + "build/.NETFramework/v4.8/Facades/System.Security.Cryptography.Encoding.dll", + "build/.NETFramework/v4.8/Facades/System.Security.Cryptography.Primitives.dll", + "build/.NETFramework/v4.8/Facades/System.Security.Cryptography.X509Certificates.dll", + "build/.NETFramework/v4.8/Facades/System.Security.Principal.dll", + "build/.NETFramework/v4.8/Facades/System.Security.SecureString.dll", + "build/.NETFramework/v4.8/Facades/System.ServiceModel.Duplex.dll", + "build/.NETFramework/v4.8/Facades/System.ServiceModel.Http.dll", + "build/.NETFramework/v4.8/Facades/System.ServiceModel.NetTcp.dll", + "build/.NETFramework/v4.8/Facades/System.ServiceModel.Primitives.dll", + "build/.NETFramework/v4.8/Facades/System.ServiceModel.Security.dll", + "build/.NETFramework/v4.8/Facades/System.Text.Encoding.Extensions.dll", + "build/.NETFramework/v4.8/Facades/System.Text.Encoding.dll", + "build/.NETFramework/v4.8/Facades/System.Text.RegularExpressions.dll", + "build/.NETFramework/v4.8/Facades/System.Threading.Overlapped.dll", + "build/.NETFramework/v4.8/Facades/System.Threading.Tasks.Parallel.dll", + "build/.NETFramework/v4.8/Facades/System.Threading.Tasks.dll", + "build/.NETFramework/v4.8/Facades/System.Threading.Thread.dll", + "build/.NETFramework/v4.8/Facades/System.Threading.ThreadPool.dll", + "build/.NETFramework/v4.8/Facades/System.Threading.Timer.dll", + "build/.NETFramework/v4.8/Facades/System.Threading.dll", + "build/.NETFramework/v4.8/Facades/System.ValueTuple.dll", + "build/.NETFramework/v4.8/Facades/System.Xml.ReaderWriter.dll", + "build/.NETFramework/v4.8/Facades/System.Xml.XDocument.dll", + "build/.NETFramework/v4.8/Facades/System.Xml.XPath.XDocument.dll", + "build/.NETFramework/v4.8/Facades/System.Xml.XPath.dll", + "build/.NETFramework/v4.8/Facades/System.Xml.XmlDocument.dll", + "build/.NETFramework/v4.8/Facades/System.Xml.XmlSerializer.dll", + "build/.NETFramework/v4.8/Facades/netstandard.dll", + "build/.NETFramework/v4.8/ISymWrapper.dll", + "build/.NETFramework/v4.8/ISymWrapper.xml", + "build/.NETFramework/v4.8/Microsoft.Activities.Build.dll", + "build/.NETFramework/v4.8/Microsoft.Activities.Build.xml", + "build/.NETFramework/v4.8/Microsoft.Build.Conversion.v4.0.dll", + "build/.NETFramework/v4.8/Microsoft.Build.Conversion.v4.0.xml", + "build/.NETFramework/v4.8/Microsoft.Build.Engine.dll", + "build/.NETFramework/v4.8/Microsoft.Build.Engine.xml", + "build/.NETFramework/v4.8/Microsoft.Build.Framework.dll", + "build/.NETFramework/v4.8/Microsoft.Build.Framework.xml", + "build/.NETFramework/v4.8/Microsoft.Build.Tasks.v4.0.dll", + "build/.NETFramework/v4.8/Microsoft.Build.Tasks.v4.0.xml", + "build/.NETFramework/v4.8/Microsoft.Build.Utilities.v4.0.dll", + "build/.NETFramework/v4.8/Microsoft.Build.Utilities.v4.0.xml", + "build/.NETFramework/v4.8/Microsoft.Build.dll", + "build/.NETFramework/v4.8/Microsoft.Build.xml", + "build/.NETFramework/v4.8/Microsoft.CSharp.dll", + "build/.NETFramework/v4.8/Microsoft.CSharp.xml", + "build/.NETFramework/v4.8/Microsoft.JScript.dll", + "build/.NETFramework/v4.8/Microsoft.JScript.xml", + "build/.NETFramework/v4.8/Microsoft.VisualBasic.Compatibility.Data.dll", + "build/.NETFramework/v4.8/Microsoft.VisualBasic.Compatibility.Data.xml", + "build/.NETFramework/v4.8/Microsoft.VisualBasic.Compatibility.dll", + "build/.NETFramework/v4.8/Microsoft.VisualBasic.Compatibility.xml", + "build/.NETFramework/v4.8/Microsoft.VisualBasic.dll", + "build/.NETFramework/v4.8/Microsoft.VisualBasic.xml", + "build/.NETFramework/v4.8/Microsoft.VisualC.STLCLR.dll", + "build/.NETFramework/v4.8/Microsoft.VisualC.STLCLR.xml", + "build/.NETFramework/v4.8/Microsoft.VisualC.dll", + "build/.NETFramework/v4.8/Microsoft.VisualC.xml", + "build/.NETFramework/v4.8/PermissionSets/FullTrust.xml", + "build/.NETFramework/v4.8/PermissionSets/Internet.xml", + "build/.NETFramework/v4.8/PermissionSets/LocalIntranet.xml", + "build/.NETFramework/v4.8/PresentationBuildTasks.dll", + "build/.NETFramework/v4.8/PresentationBuildTasks.xml", + "build/.NETFramework/v4.8/PresentationCore.dll", + "build/.NETFramework/v4.8/PresentationCore.xml", + "build/.NETFramework/v4.8/PresentationFramework.Aero.dll", + "build/.NETFramework/v4.8/PresentationFramework.Aero.xml", + "build/.NETFramework/v4.8/PresentationFramework.Aero2.dll", + "build/.NETFramework/v4.8/PresentationFramework.Aero2.xml", + "build/.NETFramework/v4.8/PresentationFramework.AeroLite.dll", + "build/.NETFramework/v4.8/PresentationFramework.AeroLite.xml", + "build/.NETFramework/v4.8/PresentationFramework.Classic.dll", + "build/.NETFramework/v4.8/PresentationFramework.Classic.xml", + "build/.NETFramework/v4.8/PresentationFramework.Luna.dll", + "build/.NETFramework/v4.8/PresentationFramework.Luna.xml", + "build/.NETFramework/v4.8/PresentationFramework.Royale.dll", + "build/.NETFramework/v4.8/PresentationFramework.Royale.xml", + "build/.NETFramework/v4.8/PresentationFramework.dll", + "build/.NETFramework/v4.8/PresentationFramework.xml", + "build/.NETFramework/v4.8/ReachFramework.dll", + "build/.NETFramework/v4.8/ReachFramework.xml", + "build/.NETFramework/v4.8/RedistList/FrameworkList.xml", + "build/.NETFramework/v4.8/System.Activities.Core.Presentation.dll", + "build/.NETFramework/v4.8/System.Activities.Core.Presentation.xml", + "build/.NETFramework/v4.8/System.Activities.DurableInstancing.dll", + "build/.NETFramework/v4.8/System.Activities.DurableInstancing.xml", + "build/.NETFramework/v4.8/System.Activities.Presentation.dll", + "build/.NETFramework/v4.8/System.Activities.Presentation.xml", + "build/.NETFramework/v4.8/System.Activities.dll", + "build/.NETFramework/v4.8/System.Activities.xml", + "build/.NETFramework/v4.8/System.AddIn.Contract.dll", + "build/.NETFramework/v4.8/System.AddIn.Contract.xml", + "build/.NETFramework/v4.8/System.AddIn.dll", + "build/.NETFramework/v4.8/System.AddIn.xml", + "build/.NETFramework/v4.8/System.ComponentModel.Composition.Registration.dll", + "build/.NETFramework/v4.8/System.ComponentModel.Composition.Registration.xml", + "build/.NETFramework/v4.8/System.ComponentModel.Composition.dll", + "build/.NETFramework/v4.8/System.ComponentModel.Composition.xml", + "build/.NETFramework/v4.8/System.ComponentModel.DataAnnotations.dll", + "build/.NETFramework/v4.8/System.ComponentModel.DataAnnotations.xml", + "build/.NETFramework/v4.8/System.Configuration.Install.dll", + "build/.NETFramework/v4.8/System.Configuration.Install.xml", + "build/.NETFramework/v4.8/System.Configuration.dll", + "build/.NETFramework/v4.8/System.Configuration.xml", + "build/.NETFramework/v4.8/System.Core.dll", + "build/.NETFramework/v4.8/System.Core.xml", + "build/.NETFramework/v4.8/System.Data.DataSetExtensions.dll", + "build/.NETFramework/v4.8/System.Data.DataSetExtensions.xml", + "build/.NETFramework/v4.8/System.Data.Entity.Design.dll", + "build/.NETFramework/v4.8/System.Data.Entity.Design.xml", + "build/.NETFramework/v4.8/System.Data.Entity.dll", + "build/.NETFramework/v4.8/System.Data.Entity.xml", + "build/.NETFramework/v4.8/System.Data.Linq.dll", + "build/.NETFramework/v4.8/System.Data.Linq.xml", + "build/.NETFramework/v4.8/System.Data.OracleClient.dll", + "build/.NETFramework/v4.8/System.Data.OracleClient.xml", + "build/.NETFramework/v4.8/System.Data.Services.Client.dll", + "build/.NETFramework/v4.8/System.Data.Services.Client.xml", + "build/.NETFramework/v4.8/System.Data.Services.Design.dll", + "build/.NETFramework/v4.8/System.Data.Services.Design.xml", + "build/.NETFramework/v4.8/System.Data.Services.dll", + "build/.NETFramework/v4.8/System.Data.Services.xml", + "build/.NETFramework/v4.8/System.Data.SqlXml.dll", + "build/.NETFramework/v4.8/System.Data.SqlXml.xml", + "build/.NETFramework/v4.8/System.Data.dll", + "build/.NETFramework/v4.8/System.Data.xml", + "build/.NETFramework/v4.8/System.Deployment.dll", + "build/.NETFramework/v4.8/System.Deployment.xml", + "build/.NETFramework/v4.8/System.Design.dll", + "build/.NETFramework/v4.8/System.Design.xml", + "build/.NETFramework/v4.8/System.Device.dll", + "build/.NETFramework/v4.8/System.Device.xml", + "build/.NETFramework/v4.8/System.Diagnostics.Tracing.dll", + "build/.NETFramework/v4.8/System.Diagnostics.Tracing.xml", + "build/.NETFramework/v4.8/System.DirectoryServices.AccountManagement.dll", + "build/.NETFramework/v4.8/System.DirectoryServices.AccountManagement.xml", + "build/.NETFramework/v4.8/System.DirectoryServices.Protocols.dll", + "build/.NETFramework/v4.8/System.DirectoryServices.Protocols.xml", + "build/.NETFramework/v4.8/System.DirectoryServices.dll", + "build/.NETFramework/v4.8/System.DirectoryServices.xml", + "build/.NETFramework/v4.8/System.Drawing.Design.dll", + "build/.NETFramework/v4.8/System.Drawing.Design.xml", + "build/.NETFramework/v4.8/System.Drawing.dll", + "build/.NETFramework/v4.8/System.Drawing.xml", + "build/.NETFramework/v4.8/System.Dynamic.dll", + "build/.NETFramework/v4.8/System.EnterpriseServices.Thunk.dll", + "build/.NETFramework/v4.8/System.EnterpriseServices.Wrapper.dll", + "build/.NETFramework/v4.8/System.EnterpriseServices.dll", + "build/.NETFramework/v4.8/System.EnterpriseServices.xml", + "build/.NETFramework/v4.8/System.IO.Compression.FileSystem.dll", + "build/.NETFramework/v4.8/System.IO.Compression.FileSystem.xml", + "build/.NETFramework/v4.8/System.IO.Compression.dll", + "build/.NETFramework/v4.8/System.IO.Compression.xml", + "build/.NETFramework/v4.8/System.IO.Log.dll", + "build/.NETFramework/v4.8/System.IO.Log.xml", + "build/.NETFramework/v4.8/System.IdentityModel.Selectors.dll", + "build/.NETFramework/v4.8/System.IdentityModel.Selectors.xml", + "build/.NETFramework/v4.8/System.IdentityModel.Services.dll", + "build/.NETFramework/v4.8/System.IdentityModel.Services.xml", + "build/.NETFramework/v4.8/System.IdentityModel.dll", + "build/.NETFramework/v4.8/System.IdentityModel.xml", + "build/.NETFramework/v4.8/System.Linq.xml", + "build/.NETFramework/v4.8/System.Management.Instrumentation.dll", + "build/.NETFramework/v4.8/System.Management.Instrumentation.xml", + "build/.NETFramework/v4.8/System.Management.dll", + "build/.NETFramework/v4.8/System.Management.xml", + "build/.NETFramework/v4.8/System.Messaging.dll", + "build/.NETFramework/v4.8/System.Messaging.xml", + "build/.NETFramework/v4.8/System.Net.Http.WebRequest.dll", + "build/.NETFramework/v4.8/System.Net.Http.WebRequest.xml", + "build/.NETFramework/v4.8/System.Net.Http.dll", + "build/.NETFramework/v4.8/System.Net.Http.xml", + "build/.NETFramework/v4.8/System.Net.dll", + "build/.NETFramework/v4.8/System.Net.xml", + "build/.NETFramework/v4.8/System.Numerics.dll", + "build/.NETFramework/v4.8/System.Numerics.xml", + "build/.NETFramework/v4.8/System.Printing.dll", + "build/.NETFramework/v4.8/System.Printing.xml", + "build/.NETFramework/v4.8/System.Reflection.Context.dll", + "build/.NETFramework/v4.8/System.Reflection.Context.xml", + "build/.NETFramework/v4.8/System.Runtime.Caching.dll", + "build/.NETFramework/v4.8/System.Runtime.Caching.xml", + "build/.NETFramework/v4.8/System.Runtime.DurableInstancing.dll", + "build/.NETFramework/v4.8/System.Runtime.DurableInstancing.xml", + "build/.NETFramework/v4.8/System.Runtime.Remoting.dll", + "build/.NETFramework/v4.8/System.Runtime.Remoting.xml", + "build/.NETFramework/v4.8/System.Runtime.Serialization.Formatters.Soap.dll", + "build/.NETFramework/v4.8/System.Runtime.Serialization.Formatters.Soap.xml", + "build/.NETFramework/v4.8/System.Runtime.Serialization.dll", + "build/.NETFramework/v4.8/System.Runtime.Serialization.xml", + "build/.NETFramework/v4.8/System.Security.dll", + "build/.NETFramework/v4.8/System.Security.xml", + "build/.NETFramework/v4.8/System.ServiceModel.Activation.dll", + "build/.NETFramework/v4.8/System.ServiceModel.Activation.xml", + "build/.NETFramework/v4.8/System.ServiceModel.Activities.dll", + "build/.NETFramework/v4.8/System.ServiceModel.Activities.xml", + "build/.NETFramework/v4.8/System.ServiceModel.Channels.dll", + "build/.NETFramework/v4.8/System.ServiceModel.Channels.xml", + "build/.NETFramework/v4.8/System.ServiceModel.Discovery.dll", + "build/.NETFramework/v4.8/System.ServiceModel.Discovery.xml", + "build/.NETFramework/v4.8/System.ServiceModel.Routing.dll", + "build/.NETFramework/v4.8/System.ServiceModel.Routing.xml", + "build/.NETFramework/v4.8/System.ServiceModel.Web.dll", + "build/.NETFramework/v4.8/System.ServiceModel.Web.xml", + "build/.NETFramework/v4.8/System.ServiceModel.dll", + "build/.NETFramework/v4.8/System.ServiceModel.xml", + "build/.NETFramework/v4.8/System.ServiceProcess.dll", + "build/.NETFramework/v4.8/System.ServiceProcess.xml", + "build/.NETFramework/v4.8/System.Speech.dll", + "build/.NETFramework/v4.8/System.Speech.xml", + "build/.NETFramework/v4.8/System.Threading.Tasks.Dataflow.xml", + "build/.NETFramework/v4.8/System.Transactions.dll", + "build/.NETFramework/v4.8/System.Transactions.xml", + "build/.NETFramework/v4.8/System.Web.Abstractions.dll", + "build/.NETFramework/v4.8/System.Web.ApplicationServices.dll", + "build/.NETFramework/v4.8/System.Web.ApplicationServices.xml", + "build/.NETFramework/v4.8/System.Web.DataVisualization.Design.dll", + "build/.NETFramework/v4.8/System.Web.DataVisualization.dll", + "build/.NETFramework/v4.8/System.Web.DataVisualization.xml", + "build/.NETFramework/v4.8/System.Web.DynamicData.Design.dll", + "build/.NETFramework/v4.8/System.Web.DynamicData.Design.xml", + "build/.NETFramework/v4.8/System.Web.DynamicData.dll", + "build/.NETFramework/v4.8/System.Web.DynamicData.xml", + "build/.NETFramework/v4.8/System.Web.Entity.Design.dll", + "build/.NETFramework/v4.8/System.Web.Entity.Design.xml", + "build/.NETFramework/v4.8/System.Web.Entity.dll", + "build/.NETFramework/v4.8/System.Web.Entity.xml", + "build/.NETFramework/v4.8/System.Web.Extensions.Design.dll", + "build/.NETFramework/v4.8/System.Web.Extensions.Design.xml", + "build/.NETFramework/v4.8/System.Web.Extensions.dll", + "build/.NETFramework/v4.8/System.Web.Extensions.xml", + "build/.NETFramework/v4.8/System.Web.Mobile.dll", + "build/.NETFramework/v4.8/System.Web.Mobile.xml", + "build/.NETFramework/v4.8/System.Web.RegularExpressions.dll", + "build/.NETFramework/v4.8/System.Web.RegularExpressions.xml", + "build/.NETFramework/v4.8/System.Web.Routing.dll", + "build/.NETFramework/v4.8/System.Web.Services.dll", + "build/.NETFramework/v4.8/System.Web.Services.xml", + "build/.NETFramework/v4.8/System.Web.dll", + "build/.NETFramework/v4.8/System.Web.xml", + "build/.NETFramework/v4.8/System.Windows.Controls.Ribbon.dll", + "build/.NETFramework/v4.8/System.Windows.Controls.Ribbon.xml", + "build/.NETFramework/v4.8/System.Windows.Forms.DataVisualization.Design.dll", + "build/.NETFramework/v4.8/System.Windows.Forms.DataVisualization.dll", + "build/.NETFramework/v4.8/System.Windows.Forms.DataVisualization.xml", + "build/.NETFramework/v4.8/System.Windows.Forms.dll", + "build/.NETFramework/v4.8/System.Windows.Forms.xml", + "build/.NETFramework/v4.8/System.Windows.Input.Manipulations.dll", + "build/.NETFramework/v4.8/System.Windows.Input.Manipulations.xml", + "build/.NETFramework/v4.8/System.Windows.Presentation.dll", + "build/.NETFramework/v4.8/System.Windows.Presentation.xml", + "build/.NETFramework/v4.8/System.Windows.dll", + "build/.NETFramework/v4.8/System.Workflow.Activities.dll", + "build/.NETFramework/v4.8/System.Workflow.Activities.xml", + "build/.NETFramework/v4.8/System.Workflow.ComponentModel.dll", + "build/.NETFramework/v4.8/System.Workflow.ComponentModel.xml", + "build/.NETFramework/v4.8/System.Workflow.Runtime.dll", + "build/.NETFramework/v4.8/System.Workflow.Runtime.xml", + "build/.NETFramework/v4.8/System.WorkflowServices.dll", + "build/.NETFramework/v4.8/System.WorkflowServices.xml", + "build/.NETFramework/v4.8/System.Xaml.dll", + "build/.NETFramework/v4.8/System.Xaml.xml", + "build/.NETFramework/v4.8/System.Xml.Linq.dll", + "build/.NETFramework/v4.8/System.Xml.Linq.xml", + "build/.NETFramework/v4.8/System.Xml.Serialization.dll", + "build/.NETFramework/v4.8/System.Xml.dll", + "build/.NETFramework/v4.8/System.Xml.xml", + "build/.NETFramework/v4.8/System.dll", + "build/.NETFramework/v4.8/System.xml", + "build/.NETFramework/v4.8/UIAutomationClient.dll", + "build/.NETFramework/v4.8/UIAutomationClient.xml", + "build/.NETFramework/v4.8/UIAutomationClientsideProviders.dll", + "build/.NETFramework/v4.8/UIAutomationClientsideProviders.xml", + "build/.NETFramework/v4.8/UIAutomationProvider.dll", + "build/.NETFramework/v4.8/UIAutomationProvider.xml", + "build/.NETFramework/v4.8/UIAutomationTypes.dll", + "build/.NETFramework/v4.8/UIAutomationTypes.xml", + "build/.NETFramework/v4.8/WindowsBase.dll", + "build/.NETFramework/v4.8/WindowsBase.xml", + "build/.NETFramework/v4.8/WindowsFormsIntegration.dll", + "build/.NETFramework/v4.8/WindowsFormsIntegration.xml", + "build/.NETFramework/v4.8/XamlBuildTask.dll", + "build/.NETFramework/v4.8/XamlBuildTask.xml", + "build/.NETFramework/v4.8/mscorlib.dll", + "build/.NETFramework/v4.8/mscorlib.xml", + "build/.NETFramework/v4.8/namespaces.xml", + "build/.NETFramework/v4.8/sysglobl.dll", + "build/.NETFramework/v4.8/sysglobl.xml", + "build/Microsoft.NETFramework.ReferenceAssemblies.net48.targets", + "microsoft.netframework.referenceassemblies.net48.1.0.3.nupkg.sha512", + "microsoft.netframework.referenceassemblies.net48.nuspec" + ] + }, + "System.Buffers/4.5.1": { + "sha512": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==", + "type": "package", + "path": "system.buffers/4.5.1", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net461/System.Buffers.dll", + "lib/net461/System.Buffers.xml", + "lib/netcoreapp2.0/_._", + "lib/netstandard1.1/System.Buffers.dll", + "lib/netstandard1.1/System.Buffers.xml", + "lib/netstandard2.0/System.Buffers.dll", + "lib/netstandard2.0/System.Buffers.xml", + "lib/uap10.0.16299/_._", + "ref/net45/System.Buffers.dll", + "ref/net45/System.Buffers.xml", + "ref/netcoreapp2.0/_._", + "ref/netstandard1.1/System.Buffers.dll", + "ref/netstandard1.1/System.Buffers.xml", + "ref/netstandard2.0/System.Buffers.dll", + "ref/netstandard2.0/System.Buffers.xml", + "ref/uap10.0.16299/_._", + "system.buffers.4.5.1.nupkg.sha512", + "system.buffers.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Memory/4.5.5": { + "sha512": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "type": "package", + "path": "system.memory/4.5.5", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net461/System.Memory.dll", + "lib/net461/System.Memory.xml", + "lib/netcoreapp2.1/_._", + "lib/netstandard1.1/System.Memory.dll", + "lib/netstandard1.1/System.Memory.xml", + "lib/netstandard2.0/System.Memory.dll", + "lib/netstandard2.0/System.Memory.xml", + "ref/netcoreapp2.1/_._", + "system.memory.4.5.5.nupkg.sha512", + "system.memory.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Numerics.Vectors/4.5.0": { + "sha512": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==", + "type": "package", + "path": "system.numerics.vectors/4.5.0", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Numerics.Vectors.dll", + "lib/net46/System.Numerics.Vectors.xml", + "lib/netcoreapp2.0/_._", + "lib/netstandard1.0/System.Numerics.Vectors.dll", + "lib/netstandard1.0/System.Numerics.Vectors.xml", + "lib/netstandard2.0/System.Numerics.Vectors.dll", + "lib/netstandard2.0/System.Numerics.Vectors.xml", + "lib/portable-net45+win8+wp8+wpa81/System.Numerics.Vectors.dll", + "lib/portable-net45+win8+wp8+wpa81/System.Numerics.Vectors.xml", + "lib/uap10.0.16299/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "lib/xamarintvos10/_._", + "lib/xamarinwatchos10/_._", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/System.Numerics.Vectors.dll", + "ref/net45/System.Numerics.Vectors.xml", + "ref/net46/System.Numerics.Vectors.dll", + "ref/net46/System.Numerics.Vectors.xml", + "ref/netcoreapp2.0/_._", + "ref/netstandard1.0/System.Numerics.Vectors.dll", + "ref/netstandard1.0/System.Numerics.Vectors.xml", + "ref/netstandard2.0/System.Numerics.Vectors.dll", + "ref/netstandard2.0/System.Numerics.Vectors.xml", + "ref/uap10.0.16299/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "ref/xamarintvos10/_._", + "ref/xamarinwatchos10/_._", + "system.numerics.vectors.4.5.0.nupkg.sha512", + "system.numerics.vectors.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + }, + "System.Runtime.CompilerServices.Unsafe/4.5.3": { + "sha512": "3TIsJhD1EiiT0w2CcDMN/iSSwnNnsrnbzeVHSKkaEgV85txMprmuO+Yq2AdSbeVGcg28pdNDTPK87tJhX7VFHw==", + "type": "package", + "path": "system.runtime.compilerservices.unsafe/4.5.3", + "files": [ + ".nupkg.metadata", + ".signature.p7s", + "LICENSE.TXT", + "THIRD-PARTY-NOTICES.TXT", + "lib/net461/System.Runtime.CompilerServices.Unsafe.dll", + "lib/net461/System.Runtime.CompilerServices.Unsafe.xml", + "lib/netcoreapp2.0/System.Runtime.CompilerServices.Unsafe.dll", + "lib/netcoreapp2.0/System.Runtime.CompilerServices.Unsafe.xml", + "lib/netstandard1.0/System.Runtime.CompilerServices.Unsafe.dll", + "lib/netstandard1.0/System.Runtime.CompilerServices.Unsafe.xml", + "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll", + "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.xml", + "ref/net461/System.Runtime.CompilerServices.Unsafe.dll", + "ref/net461/System.Runtime.CompilerServices.Unsafe.xml", + "ref/netstandard1.0/System.Runtime.CompilerServices.Unsafe.dll", + "ref/netstandard1.0/System.Runtime.CompilerServices.Unsafe.xml", + "ref/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll", + "ref/netstandard2.0/System.Runtime.CompilerServices.Unsafe.xml", + "system.runtime.compilerservices.unsafe.4.5.3.nupkg.sha512", + "system.runtime.compilerservices.unsafe.nuspec", + "useSharedDesignerContext.txt", + "version.txt" + ] + } + }, + "projectFileDependencyGroups": { + ".NETFramework,Version=v4.8": [ + "Microsoft.NETFramework.ReferenceAssemblies >= 1.0.3", + "System.Memory >= 4.5.5" + ] + }, + "packageFolders": { + "/home/jskala/.nuget/packages/": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj", + "projectName": "NFOGuard.Emby.Plugin", + "projectPath": "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj", + "packagesPath": "/home/jskala/.nuget/packages/", + "outputPath": "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/obj/", + "projectStyle": "PackageReference", + "configFilePaths": [ + "/home/jskala/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net48" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net48": { + "targetAlias": "net48", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + } + }, + "frameworks": { + "net48": { + "targetAlias": "net48", + "dependencies": { + "Microsoft.NETFramework.ReferenceAssemblies": { + "suppressParent": "All", + "target": "Package", + "version": "[1.0.3, )", + "autoReferenced": true + }, + "System.Memory": { + "target": "Package", + "version": "[4.5.5, )" + } + }, + "runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/8.0.414/RuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/obj/project.nuget.cache b/NFOGuard.Emby.Plugin/obj/project.nuget.cache new file mode 100644 index 0000000..d4c5707 --- /dev/null +++ b/NFOGuard.Emby.Plugin/obj/project.nuget.cache @@ -0,0 +1,15 @@ +{ + "version": 2, + "dgSpecHash": "+Vupasg2dYE=", + "success": true, + "projectFilePath": "/home/jskala/github/NFOguard/NFOGuard.Emby.Plugin/NFOGuard.Emby.Plugin.csproj", + "expectedPackageFiles": [ + "/home/jskala/.nuget/packages/microsoft.netframework.referenceassemblies/1.0.3/microsoft.netframework.referenceassemblies.1.0.3.nupkg.sha512", + "/home/jskala/.nuget/packages/microsoft.netframework.referenceassemblies.net48/1.0.3/microsoft.netframework.referenceassemblies.net48.1.0.3.nupkg.sha512", + "/home/jskala/.nuget/packages/system.buffers/4.5.1/system.buffers.4.5.1.nupkg.sha512", + "/home/jskala/.nuget/packages/system.memory/4.5.5/system.memory.4.5.5.nupkg.sha512", + "/home/jskala/.nuget/packages/system.numerics.vectors/4.5.0/system.numerics.vectors.4.5.0.nupkg.sha512", + "/home/jskala/.nuget/packages/system.runtime.compilerservices.unsafe/4.5.3/system.runtime.compilerservices.unsafe.4.5.3.nupkg.sha512" + ], + "logs": [] +} \ No newline at end of file diff --git a/NFOGuard.Emby.Plugin/plugin.xml b/NFOGuard.Emby.Plugin/plugin.xml new file mode 100644 index 0000000..c86ac4f --- /dev/null +++ b/NFOGuard.Emby.Plugin/plugin.xml @@ -0,0 +1,22 @@ + + + NFOGuard + Synchronizes TV episode PremiereDate to DateCreated to preserve import dates in Emby + This plugin automatically syncs PremiereDate to DateCreated for TV episodes to ensure Emby displays the correct import date from NFO files instead of scan dates. + 1.0.0.0 + 4.7.0.0 + 4.8.99.99 + NFOGuard Team + General + thumb.png + https://github.com/NFOguard/NFOguard + false + false + NFOGuard.Emby.Plugin.dll + https://github.com/NFOguard/NFOguard/releases/latest/NFOGuard.Emby.Plugin.dll + + + Initial release - TV episode date synchronization + + + \ No newline at end of file diff --git a/README.md b/README.md index b4f9c29..188414e 100644 --- a/README.md +++ b/README.md @@ -463,6 +463,22 @@ ALLOW_FILE_DATE_FALLBACK=false - ⚠️ Movies with NO release dates get skipped (no NFO created) - 🔇 No more "Using file dateAdded as fallback" warnings +## 📱 Media Server Compatibility + +### Movies +- **Emby**: ✅ Full support - reads NFO `` fields +- **Jellyfin**: ✅ Full support - reads NFO `` fields +- **Plex**: ✅ Full support - reads NFO `` fields + +### TV Episodes +- **Emby**: ⚠️ **Partial support** - reads metadata (titles, plots, ratings) but **ignores all NFO date fields** +- **Jellyfin**: ✅ Full support - reads NFO `` fields +- **Plex**: ✅ Full support - reads NFO `` fields + +> **Note**: Emby has an architectural limitation where TV episodes always use filesystem dates instead of NFO dates. This is a known Emby behavior that cannot be worked around. NFOGuard still provides valuable enhanced metadata for TV episodes in Emby, just not corrected dates. + +--- + ## 📖 Example Workflow You add The Blacklist in Sonarr. diff --git a/VERSION b/VERSION index a918a2a..ee6cdce 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.0 +0.6.1 diff --git a/decompiled-TimeLord/Emby.Security/PluginSecurityManager.cs b/decompiled-TimeLord/Emby.Security/PluginSecurityManager.cs new file mode 100644 index 0000000..a343a0c --- /dev/null +++ b/decompiled-TimeLord/Emby.Security/PluginSecurityManager.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.Security; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; + +namespace Emby.Security; + +public class PluginSecurityManager : IServerEntryPoint, IDisposable +{ + private const string MBValidateUrl = "https://mb3admin.com/admin/service/registration/validate"; + + private readonly IHttpClient _httpClient; + + private readonly IJsonSerializer _jsonSerializer; + + private readonly IServerApplicationHost _appHost; + + private readonly ILogger _logger; + + private readonly IApplicationPaths _appPaths; + + private readonly IFileSystem _fileSystem; + + private readonly ICryptoProvider _cryptographyProvider; + + private readonly ISecurityManager _securityManager; + + private readonly IEncryptionManager _encryption; + + public static PluginSecurityManager Current; + + private readonly Dictionary _regRecordCache = new Dictionary(); + + private SemaphoreSlim _regLock = new SemaphoreSlim(1, 1); + + public PluginSecurityManager(IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, ILogManager logManager, IFileSystem fileSystem, ICryptoProvider cryptographyProvider, ISecurityManager securityManager, IEncryptionManager encryption) + { + if (httpClient == null) + { + throw new ArgumentNullException("httpClient"); + } + _appHost = appHost; + _httpClient = httpClient; + _jsonSerializer = jsonSerializer; + _appPaths = appPaths; + _fileSystem = fileSystem; + _cryptographyProvider = cryptographyProvider; + _securityManager = securityManager; + _encryption = encryption; + _logger = logManager.GetLogger("Plugin"); + Current = this; + } + + public void Run() + { + } + + public void Dispose() + { + } + + private string GetCacheFilePath(string feature) + { + string text = GetType().FullName + GetCacheKey(feature); + text = BaseExtensions.GetMD5(text).ToString("N"); + return Path.Combine(_appPaths.PluginConfigurationsPath, text); + } + + private RegRecord GetCachedRegRecord(string feature) + { + lock (_regRecordCache) + { + if (_regRecordCache.TryGetValue(GetCacheKey(feature), out var value)) + { + return value; + } + } + string cacheFilePath = GetCacheFilePath(feature); + try + { + string text = _encryption.DecryptString(_fileSystem.ReadAllText(cacheFilePath)); + RegRecord value = _jsonSerializer.DeserializeFromString(text); + if (value != null) + { + return value; + } + } + catch (Exception) + { + } + return null; + } + + private string GetCacheKey(string feature) + { + string text = _securityManager.SupporterKey; + if (string.IsNullOrWhiteSpace(text)) + { + text = "none"; + } + return text + "-" + feature; + } + + private void DeleteCachedRecord(string feature) + { + lock (_regRecordCache) + { + _regRecordCache.Remove(GetCacheKey(feature)); + } + try + { + _fileSystem.DeleteFile(GetCacheFilePath(feature)); + } + catch + { + } + } + + private void SaveCachedRecord(string feature, RegRecord record) + { + lock (_regRecordCache) + { + _regRecordCache[GetCacheKey(feature)] = record; + } + string cacheFilePath = GetCacheFilePath(feature); + string text = _jsonSerializer.SerializeToString((object)record); + string text2 = _encryption.EncryptString(text); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath)); + _fileSystem.WriteAllText(cacheFilePath, text2); + } + + public async Task GetRegistrationStatus(string feature, CancellationToken cancellationToken) + { + await _regLock.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); + try + { + return await GetRegistrationStatusInternal(feature, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); + } + finally + { + _regLock.Release(); + } + } + + private bool CheckRegistrationWithServer(DateTime lastChecked, bool registered) + { + if (registered || string.IsNullOrWhiteSpace(_securityManager.SupporterKey)) + { + if (DateTime.UtcNow.AddDays(-1.0) > lastChecked) + { + return true; + } + } + else if (DateTime.UtcNow.AddHours(-1.0) > lastChecked) + { + return true; + } + return false; + } + + private async Task GetRegistrationStatusInternal(string feature, CancellationToken cancellationToken) + { + RegRecord regInfo = GetCachedRegRecord(feature); + DateTime lastChecked = regInfo?.lastChecked ?? DateTime.MinValue; + DateTime expDate = regInfo?.expDate ?? DateTime.MinValue; + bool registered = regInfo?.registered ?? false; + string supporterKey = _securityManager.SupporterKey; + if (CheckRegistrationWithServer(lastChecked, registered)) + { + Dictionary postData = new Dictionary + { + { "feature", feature }, + { "key", supporterKey }, + { + "mac", + ((IApplicationHost)_appHost).SystemId + }, + { + "systemid", + ((IApplicationHost)_appHost).SystemId + }, + { + "platform", + ((IApplicationHost)_appHost).OperatingSystemDisplayName + } + }; + try + { + HttpRequestOptions val = new HttpRequestOptions + { + Url = "https://mb3admin.com/admin/service/registration/validate", + EnableHttpCompression = false, + BufferContent = false, + CancellationToken = cancellationToken + }; + val.SetPostData((IDictionary)postData); + HttpResponseInfo val2 = await _httpClient.SendAsync(val, "POST").ConfigureAwait(continueOnCapturedContext: false); + try + { + using Stream stream = val2.Content; + regInfo = _jsonSerializer.DeserializeFromStream(stream); + regInfo.lastChecked = DateTime.UtcNow; + SaveCachedRecord(feature, regInfo); + } + finally + { + ((IDisposable)val2)?.Dispose(); + } + } + catch (Exception ex) + { + _logger.ErrorException("Error checking registration status of {0}", ex, new object[1] { feature }); + int num = 14; + DateTime dateTime = new DateTime[2] + { + expDate, + lastChecked.AddDays(num) + }.Min(); + if (dateTime > DateTime.UtcNow.AddDays(num)) + { + dateTime = DateTime.MinValue; + } + regInfo = new RegRecord + { + registered = (regInfo != null && regInfo.registered && dateTime >= DateTime.UtcNow && expDate >= DateTime.UtcNow), + expDate = expDate + }; + } + } + return regInfo; + } +} diff --git a/decompiled-TimeLord/Emby.Security/RegRecord.cs b/decompiled-TimeLord/Emby.Security/RegRecord.cs new file mode 100644 index 0000000..2f7ace2 --- /dev/null +++ b/decompiled-TimeLord/Emby.Security/RegRecord.cs @@ -0,0 +1,40 @@ +using System; + +namespace Emby.Security; + +public class RegRecord +{ + public string featId { get; set; } + + public bool registered { get; set; } + + public DateTime expDate { get; set; } + + public string key { get; set; } + + public DateTime lastChecked { get; set; } + + public bool isTrial + { + get + { + if (expDate > DateTime.UtcNow) + { + return !registered; + } + return false; + } + } + + public bool isValid + { + get + { + if (!isTrial) + { + return registered; + } + return true; + } + } +} diff --git a/decompiled-TimeLord/MediaBrowser.Plugins.TimeLord.ScheduledTasks/TLTVScheduledTask.cs b/decompiled-TimeLord/MediaBrowser.Plugins.TimeLord.ScheduledTasks/TLTVScheduledTask.cs new file mode 100644 index 0000000..634862f --- /dev/null +++ b/decompiled-TimeLord/MediaBrowser.Plugins.TimeLord.ScheduledTasks/TLTVScheduledTask.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Emby.Security; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Security; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Tasks; + +namespace MediaBrowser.Plugins.TimeLord.ScheduledTasks; + +public class TLTVScheduledTask : IScheduledTask +{ + private readonly ILogger _logger; + + private readonly ILibraryManager _libraryManager; + + private readonly IMediaEncoder _mediaEncoder; + + private readonly IFileSystem _fileSystem; + + private readonly IApplicationPaths _appPaths; + + private readonly ILibraryMonitor _libraryMonitor; + + private readonly IUserManager _userManager; + + private readonly INotificationManager _notificationManager; + + private readonly IItemRepository _itemRepo; + + public string Category => "Time Lord"; + + public string Key => "TimeLordTV"; + + public string Description => "Sets the Episode Date Added to the original airdate (Episodes)"; + + public string Name => "Change episode date added to original airdate"; + + public TLTVScheduledTask(ILibraryManager libraryManager, ILogger logger, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IApplicationPaths appPaths, ILibraryMonitor libraryMonitor, ISecurityManager securityManager, IUserManager userManager, INotificationManager notificationManager, IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _userManager = userManager; + _mediaEncoder = mediaEncoder; + _fileSystem = fileSystem; + _appPaths = appPaths; + _libraryMonitor = libraryMonitor; + _notificationManager = notificationManager; + _itemRepo = itemRepo; + } + + public async Task Execute(CancellationToken cancellationToken, IProgress progress) + { + RegRecord registration = await PluginSecurityManager.Current.GetRegistrationStatus("TimeLord", cancellationToken).ConfigureAwait(continueOnCapturedContext: false); + Plugin.Instance.Registration = registration; + _logger.Info("TimeLordTV Registration Status - Registered: {0} In trial: {2} Expiration Date: {1} Is Valid: {3}", new object[4] + { + Plugin.Instance.Registration.registered, + Plugin.Instance.Registration.expDate, + Plugin.Instance.Registration.isTrial, + Plugin.Instance.Registration.isValid + }); + if (Plugin.Instance.Registration.isTrial) + { + _logger.Info(((BasePlugin)Plugin.Instance).Name + " You are Using the Trial Version, Please Consider purchasing to continue using the plugin after the trial period", new object[0]); + } + if (!Plugin.Instance.Registration.isValid) + { + _logger.Info(((BasePlugin)Plugin.Instance).Name + " Trial Expired, Please register to continue using the plugin", new object[0]); + return; + } + _logger.Info("TimeLordTV :: Start :", (object[])null); + try + { + _logger.Info("TimeLord :: Rework Series Dates :: Start", (object[])null); + AggregateFolder rootFolder = _libraryManager.RootFolder; + InternalItemsQuery val = new InternalItemsQuery(); + val.IncludeItemTypes = new string[1] { typeof(Series).Name }; + val.Recursive = true; + BaseItem[] itemList = ((Folder)rootFolder).GetItemList(val); + int num = 0; + BaseItem[] array = itemList; + foreach (BaseItem val2 in array) + { + try + { + cancellationToken.ThrowIfCancellationRequested(); + if (val2.PremiereDate.HasValue && val2.DateCreated != val2.PremiereDate.Value && val2.PremiereDate.HasValue) + { + val2.DateCreated = val2.PremiereDate.Value; + try + { + val2.UpdateToRepository((ItemUpdateType)16); + } + catch + { + } + } + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex2) + { + _logger.ErrorException("TimeLordTV :: Error messing about with {0}", ex2, new object[1] { val2.Name }); + } + num++; + double num2 = num; + num2 *= 100.0; + progress.Report(num2); + } + } + catch (Exception ex3) + { + _logger.ErrorException("TimeLord :: Error {0}", ex3, Array.Empty()); + } + try + { + _logger.Info("TimeLord :: Rework Season Dates :: Start", (object[])null); + AggregateFolder rootFolder2 = _libraryManager.RootFolder; + InternalItemsQuery val = new InternalItemsQuery(); + val.IncludeItemTypes = new string[1] { typeof(Season).Name }; + val.Recursive = true; + BaseItem[] itemList2 = ((Folder)rootFolder2).GetItemList(val); + _logger.Info("TimeLord :: Rework Season Dates :: Count ::" + itemList2.Count(), (object[])null); + int num3 = 0; + BaseItem[] array = itemList2; + for (int i = 0; i < array.Length; i++) + { + Season val3 = (Season)array[i]; + try + { + cancellationToken.ThrowIfCancellationRequested(); + if (((BaseItem)val3).PremiereDate.HasValue && ((BaseItem)val3).DateCreated != ((BaseItem)val3).PremiereDate.Value && ((BaseItem)val3).PremiereDate.HasValue) + { + ((BaseItem)val3).DateCreated = ((BaseItem)val3).PremiereDate.Value; + try + { + ((BaseItem)val3).UpdateToRepository((ItemUpdateType)16); + _logger.Info("TimeLordTV :: Updated :: " + ((BaseItem)val3).Name, (object[])null); + } + catch + { + } + } + } + catch (OperationCanceledException) + { + _logger.Info("TimeLordTV :: Canceled", (object[])null); + break; + } + catch (Exception ex5) + { + _logger.ErrorException("TimeLordTV :: Error messing about with {0}", ex5, new object[1] { ((BaseItem)val3).Name }); + } + num3++; + double num4 = num3; + num4 /= (double)itemList2.Count(); + num4 *= 100.0; + progress.Report(num4); + } + } + catch (Exception ex6) + { + _logger.ErrorException("TimeLord :: Error {0}", ex6, Array.Empty()); + } + try + { + _logger.Info("TimeLord :: Rework Episode Dates :: Start", (object[])null); + AggregateFolder rootFolder3 = _libraryManager.RootFolder; + InternalItemsQuery val = new InternalItemsQuery(); + val.IncludeItemTypes = new string[1] { typeof(Episode).Name }; + val.Recursive = true; + List list = ((Folder)rootFolder3).GetItemList(val).Where(delegate(BaseItem val5) + { + //IL_0001: Unknown result type (might be due to invalid IL or missing references) + if ((int)val5.LocationType == 0) + { + DateTimeOffset dateCreated = val5.DateCreated; + DateTimeOffset? premiereDate = val5.PremiereDate; + if (premiereDate.HasValue && dateCreated > premiereDate.GetValueOrDefault() && val5.PremiereDate.HasValue) + { + return !val5.IsLocked; + } + } + return false; + }).ToList(); + _logger.Info("TimeLord :: Rework Episode Dates :: Count ::" + list.Count, (object[])null); + int num5 = 0; + foreach (Episode item in list) + { + Episode val4 = item; + try + { + cancellationToken.ThrowIfCancellationRequested(); + if (((BaseItem)val4).PremiereDate.HasValue && ((BaseItem)val4).DateCreated != ((BaseItem)val4).PremiereDate.Value && ((BaseItem)val4).PremiereDate.HasValue) + { + ((BaseItem)val4).DateCreated = ((BaseItem)val4).PremiereDate.Value; + try + { + ((BaseItem)val4).UpdateToRepository((ItemUpdateType)16); + _logger.Info("TimeLordTV :: Updated :: " + ((BaseItem)val4).Name, (object[])null); + } + catch + { + } + } + } + catch (OperationCanceledException) + { + _logger.Info("TimeLordTV :: Canceled", (object[])null); + break; + } + catch (Exception ex8) + { + _logger.ErrorException("TimeLordTV :: Error messing about with {0}", ex8, new object[1] { ((BaseItem)val4).Name }); + } + num5++; + double num6 = num5; + num6 /= (double)list.Count; + num6 *= 100.0; + progress.Report(num6); + } + } + catch (Exception ex9) + { + _logger.ErrorException("TimeLord :: Error {0}", ex9, Array.Empty()); + } + } + + public IEnumerable GetDefaultTriggers() + { + //IL_0008: Unknown result type (might be due to invalid IL or missing references) + //IL_000d: Unknown result type (might be due to invalid IL or missing references) + //IL_0018: Unknown result type (might be due to invalid IL or missing references) + //IL_003a: Expected O, but got Unknown + return (IEnumerable)(object)new TaskTriggerInfo[1] + { + new TaskTriggerInfo + { + Type = "DailyTrigger", + TimeOfDayTicks = TimeSpan.FromHours(5.0).Ticks + } + }; + } +} diff --git a/decompiled-TimeLord/MediaBrowser.Plugins.TimeLord.csproj b/decompiled-TimeLord/MediaBrowser.Plugins.TimeLord.csproj new file mode 100644 index 0000000..711584d --- /dev/null +++ b/decompiled-TimeLord/MediaBrowser.Plugins.TimeLord.csproj @@ -0,0 +1,23 @@ + + + MediaBrowser.Plugins.TimeLord + False + netstandard2.0 + + + 12.0 + True + + + + + + + + + + + + + + \ No newline at end of file diff --git a/decompiled-TimeLord/MediaBrowser.Plugins.TimeLord.thumb.png b/decompiled-TimeLord/MediaBrowser.Plugins.TimeLord.thumb.png new file mode 100644 index 0000000..daf4a1d Binary files /dev/null and b/decompiled-TimeLord/MediaBrowser.Plugins.TimeLord.thumb.png differ diff --git a/decompiled-TimeLord/MediaBrowser.Plugins.TimeLord/Plugin.cs b/decompiled-TimeLord/MediaBrowser.Plugins.TimeLord/Plugin.cs new file mode 100644 index 0000000..bb0a1da --- /dev/null +++ b/decompiled-TimeLord/MediaBrowser.Plugins.TimeLord/Plugin.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using Emby.Security; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Drawing; + +namespace MediaBrowser.Plugins.TimeLord; + +public class Plugin : BasePlugin, IHasThumbImage +{ + private Guid _id = new Guid("3A7EF733-F70B-4947-9E03-2813B5A423E3"); + + public RegRecord Registration; + + public override string Name => "TimeLordTV"; + + public override string Description => "Set Episode Added date to the original airdate for the Epsiode."; + + public override Guid Id => _id; + + public ImageFormat ThumbImageFormat => (ImageFormat)3; + + public static Plugin Instance { get; private set; } + + public Stream GetThumbImage() + { + Type type = ((object)this).GetType(); + return type.Assembly.GetManifestResourceStream(type.Namespace + ".thumb.png"); + } + + public Plugin() + { + Instance = this; + } +} diff --git a/decompiled-TimeLord/Properties/AssemblyInfo.cs b/decompiled-TimeLord/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e20e7e2 --- /dev/null +++ b/decompiled-TimeLord/Properties/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +[assembly: ComVisible(false)] +[assembly: Guid("3A7EF733-F70B-4947-9E03-2813B5A423E3")] +[assembly: AssemblyCompany("ScoobydooProject.com")] +[assembly: AssemblyConfiguration("Release")] +[assembly: AssemblyCopyright("Copyright © 2019 ScoobydooProject.com")] +[assembly: AssemblyDescription("Wayback Machine for TV Episodes")] +[assembly: AssemblyFileVersion("19.08.03.1105")] +[assembly: AssemblyInformationalVersion("3.2.32.14")] +[assembly: AssemblyProduct("MediaBrowser.Plugins.TimeLord")] +[assembly: AssemblyTitle("MediaBrowser.Plugins.TimeLord")] +[assembly: AssemblyVersion("19.8.3.1105")] diff --git a/decompiled-TimeLordMovies/Emby.Security/PluginSecurityManager.cs b/decompiled-TimeLordMovies/Emby.Security/PluginSecurityManager.cs new file mode 100644 index 0000000..a343a0c --- /dev/null +++ b/decompiled-TimeLordMovies/Emby.Security/PluginSecurityManager.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.Security; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; + +namespace Emby.Security; + +public class PluginSecurityManager : IServerEntryPoint, IDisposable +{ + private const string MBValidateUrl = "https://mb3admin.com/admin/service/registration/validate"; + + private readonly IHttpClient _httpClient; + + private readonly IJsonSerializer _jsonSerializer; + + private readonly IServerApplicationHost _appHost; + + private readonly ILogger _logger; + + private readonly IApplicationPaths _appPaths; + + private readonly IFileSystem _fileSystem; + + private readonly ICryptoProvider _cryptographyProvider; + + private readonly ISecurityManager _securityManager; + + private readonly IEncryptionManager _encryption; + + public static PluginSecurityManager Current; + + private readonly Dictionary _regRecordCache = new Dictionary(); + + private SemaphoreSlim _regLock = new SemaphoreSlim(1, 1); + + public PluginSecurityManager(IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, ILogManager logManager, IFileSystem fileSystem, ICryptoProvider cryptographyProvider, ISecurityManager securityManager, IEncryptionManager encryption) + { + if (httpClient == null) + { + throw new ArgumentNullException("httpClient"); + } + _appHost = appHost; + _httpClient = httpClient; + _jsonSerializer = jsonSerializer; + _appPaths = appPaths; + _fileSystem = fileSystem; + _cryptographyProvider = cryptographyProvider; + _securityManager = securityManager; + _encryption = encryption; + _logger = logManager.GetLogger("Plugin"); + Current = this; + } + + public void Run() + { + } + + public void Dispose() + { + } + + private string GetCacheFilePath(string feature) + { + string text = GetType().FullName + GetCacheKey(feature); + text = BaseExtensions.GetMD5(text).ToString("N"); + return Path.Combine(_appPaths.PluginConfigurationsPath, text); + } + + private RegRecord GetCachedRegRecord(string feature) + { + lock (_regRecordCache) + { + if (_regRecordCache.TryGetValue(GetCacheKey(feature), out var value)) + { + return value; + } + } + string cacheFilePath = GetCacheFilePath(feature); + try + { + string text = _encryption.DecryptString(_fileSystem.ReadAllText(cacheFilePath)); + RegRecord value = _jsonSerializer.DeserializeFromString(text); + if (value != null) + { + return value; + } + } + catch (Exception) + { + } + return null; + } + + private string GetCacheKey(string feature) + { + string text = _securityManager.SupporterKey; + if (string.IsNullOrWhiteSpace(text)) + { + text = "none"; + } + return text + "-" + feature; + } + + private void DeleteCachedRecord(string feature) + { + lock (_regRecordCache) + { + _regRecordCache.Remove(GetCacheKey(feature)); + } + try + { + _fileSystem.DeleteFile(GetCacheFilePath(feature)); + } + catch + { + } + } + + private void SaveCachedRecord(string feature, RegRecord record) + { + lock (_regRecordCache) + { + _regRecordCache[GetCacheKey(feature)] = record; + } + string cacheFilePath = GetCacheFilePath(feature); + string text = _jsonSerializer.SerializeToString((object)record); + string text2 = _encryption.EncryptString(text); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath)); + _fileSystem.WriteAllText(cacheFilePath, text2); + } + + public async Task GetRegistrationStatus(string feature, CancellationToken cancellationToken) + { + await _regLock.WaitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); + try + { + return await GetRegistrationStatusInternal(feature, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); + } + finally + { + _regLock.Release(); + } + } + + private bool CheckRegistrationWithServer(DateTime lastChecked, bool registered) + { + if (registered || string.IsNullOrWhiteSpace(_securityManager.SupporterKey)) + { + if (DateTime.UtcNow.AddDays(-1.0) > lastChecked) + { + return true; + } + } + else if (DateTime.UtcNow.AddHours(-1.0) > lastChecked) + { + return true; + } + return false; + } + + private async Task GetRegistrationStatusInternal(string feature, CancellationToken cancellationToken) + { + RegRecord regInfo = GetCachedRegRecord(feature); + DateTime lastChecked = regInfo?.lastChecked ?? DateTime.MinValue; + DateTime expDate = regInfo?.expDate ?? DateTime.MinValue; + bool registered = regInfo?.registered ?? false; + string supporterKey = _securityManager.SupporterKey; + if (CheckRegistrationWithServer(lastChecked, registered)) + { + Dictionary postData = new Dictionary + { + { "feature", feature }, + { "key", supporterKey }, + { + "mac", + ((IApplicationHost)_appHost).SystemId + }, + { + "systemid", + ((IApplicationHost)_appHost).SystemId + }, + { + "platform", + ((IApplicationHost)_appHost).OperatingSystemDisplayName + } + }; + try + { + HttpRequestOptions val = new HttpRequestOptions + { + Url = "https://mb3admin.com/admin/service/registration/validate", + EnableHttpCompression = false, + BufferContent = false, + CancellationToken = cancellationToken + }; + val.SetPostData((IDictionary)postData); + HttpResponseInfo val2 = await _httpClient.SendAsync(val, "POST").ConfigureAwait(continueOnCapturedContext: false); + try + { + using Stream stream = val2.Content; + regInfo = _jsonSerializer.DeserializeFromStream(stream); + regInfo.lastChecked = DateTime.UtcNow; + SaveCachedRecord(feature, regInfo); + } + finally + { + ((IDisposable)val2)?.Dispose(); + } + } + catch (Exception ex) + { + _logger.ErrorException("Error checking registration status of {0}", ex, new object[1] { feature }); + int num = 14; + DateTime dateTime = new DateTime[2] + { + expDate, + lastChecked.AddDays(num) + }.Min(); + if (dateTime > DateTime.UtcNow.AddDays(num)) + { + dateTime = DateTime.MinValue; + } + regInfo = new RegRecord + { + registered = (regInfo != null && regInfo.registered && dateTime >= DateTime.UtcNow && expDate >= DateTime.UtcNow), + expDate = expDate + }; + } + } + return regInfo; + } +} diff --git a/decompiled-TimeLordMovies/Emby.Security/RegRecord.cs b/decompiled-TimeLordMovies/Emby.Security/RegRecord.cs new file mode 100644 index 0000000..2f7ace2 --- /dev/null +++ b/decompiled-TimeLordMovies/Emby.Security/RegRecord.cs @@ -0,0 +1,40 @@ +using System; + +namespace Emby.Security; + +public class RegRecord +{ + public string featId { get; set; } + + public bool registered { get; set; } + + public DateTime expDate { get; set; } + + public string key { get; set; } + + public DateTime lastChecked { get; set; } + + public bool isTrial + { + get + { + if (expDate > DateTime.UtcNow) + { + return !registered; + } + return false; + } + } + + public bool isValid + { + get + { + if (!isTrial) + { + return registered; + } + return true; + } + } +} diff --git a/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies.ScheduledTasks/TLMoviesScheduledTask.cs b/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies.ScheduledTasks/TLMoviesScheduledTask.cs new file mode 100644 index 0000000..ba4bd10 --- /dev/null +++ b/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies.ScheduledTasks/TLMoviesScheduledTask.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Emby.Security; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Security; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Tasks; + +namespace MediaBrowser.Plugins.TimeLordMovies.ScheduledTasks; + +public class TLMoviesScheduledTask : IScheduledTask +{ + private readonly ILogger _logger; + + private readonly ILibraryManager _libraryManager; + + private readonly IMediaEncoder _mediaEncoder; + + private readonly IFileSystem _fileSystem; + + private readonly IApplicationPaths _appPaths; + + private readonly ILibraryMonitor _libraryMonitor; + + private readonly IUserManager _userManager; + + private readonly INotificationManager _notificationManager; + + private readonly IItemRepository _itemRepo; + + public string Category => "Time Lord"; + + public string Key => "TimeLordMovies"; + + public string Description => "Sets the Movie Date Added to the original premier date (Movies)"; + + public string Name => "Change Movie date added to original Premier date"; + + public TLMoviesScheduledTask(ILibraryManager libraryManager, ILogger logger, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IApplicationPaths appPaths, ILibraryMonitor libraryMonitor, ISecurityManager securityManager, IUserManager userManager, INotificationManager notificationManager, IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _userManager = userManager; + _mediaEncoder = mediaEncoder; + _fileSystem = fileSystem; + _appPaths = appPaths; + _libraryMonitor = libraryMonitor; + _notificationManager = notificationManager; + _itemRepo = itemRepo; + } + + public async Task Execute(CancellationToken cancellationToken, IProgress progress) + { + RegRecord registration = await PluginSecurityManager.Current.GetRegistrationStatus("TimeLordMovies", cancellationToken).ConfigureAwait(continueOnCapturedContext: false); + Plugin.Instance.Registration = registration; + _logger.Info("TimeLordMovies Registration Status - Registered: {0} In trial: {2} Expiration Date: {1} Is Valid: {3}", new object[4] + { + Plugin.Instance.Registration.registered, + Plugin.Instance.Registration.expDate, + Plugin.Instance.Registration.isTrial, + Plugin.Instance.Registration.isValid + }); + if (Plugin.Instance.Registration.isTrial) + { + _logger.Info(((BasePlugin)Plugin.Instance).Name + " You are Using the Trial Version, Please Consider purchasing to continue using the plugin after the trial period", new object[0]); + } + if (!Plugin.Instance.Registration.isValid) + { + _logger.Info(((BasePlugin)Plugin.Instance).Name + " Trial Expired, Please register to continue using the plugin", new object[0]); + return; + } + _logger.Info("TimeLord.Movies :: Start :", (object[])null); + try + { + _logger.Info("TimeLord.Movies :: Rework Movie Dates :: Start", (object[])null); + AggregateFolder rootFolder = _libraryManager.RootFolder; + InternalItemsQuery val = new InternalItemsQuery(); + val.IncludeItemTypes = new string[1] { typeof(Movie).Name }; + val.Recursive = true; + List list = ((Folder)rootFolder).GetItemList(val).Where(delegate(BaseItem i) + { + //IL_0001: Unknown result type (might be due to invalid IL or missing references) + if ((int)i.LocationType == 0) + { + DateTimeOffset dateCreated = i.DateCreated; + DateTimeOffset? premiereDate = i.PremiereDate; + if (premiereDate.HasValue && dateCreated > premiereDate.GetValueOrDefault() && i.PremiereDate.HasValue) + { + return !i.IsLocked; + } + } + return false; + }).ToList(); + int num = 0; + foreach (Movie item in list) + { + Movie val2 = item; + try + { + cancellationToken.ThrowIfCancellationRequested(); + ((BaseItem)val2).DateCreated = ((BaseItem)val2).PremiereDate.Value; + try + { + ((BaseItem)val2).UpdateToRepository((ItemUpdateType)16); + _logger.Info("TimeLord.Movies:: Updated :: " + ((BaseItem)val2).Name, (object[])null); + } + catch + { + _logger.Info("TimeLord.Movies :: Updated :: " + ((BaseItem)val2).Name, (object[])null); + } + } + catch (OperationCanceledException) + { + _logger.Info("TimeLord.Movies :: Canceled", (object[])null); + break; + } + catch (Exception ex2) + { + _logger.ErrorException("TimeLord.Movies :: Error messing about with {0}", ex2, new object[1] { ((BaseItem)val2).Name }); + } + num++; + double num2 = num; + num2 /= (double)list.Count(); + num2 *= 100.0; + progress.Report(num2); + } + } + catch (Exception ex3) + { + _logger.ErrorException("TimeLord.Movies :: Error {0}", ex3, Array.Empty()); + } + } + + public IEnumerable GetDefaultTriggers() + { + //IL_0008: Unknown result type (might be due to invalid IL or missing references) + //IL_000d: Unknown result type (might be due to invalid IL or missing references) + //IL_0018: Unknown result type (might be due to invalid IL or missing references) + //IL_003a: Expected O, but got Unknown + return (IEnumerable)(object)new TaskTriggerInfo[1] + { + new TaskTriggerInfo + { + Type = "DailyTrigger", + TimeOfDayTicks = TimeSpan.FromHours(5.0).Ticks + } + }; + } +} diff --git a/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies.ScheduledTasks/TLTrailersScheduledTask.cs b/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies.ScheduledTasks/TLTrailersScheduledTask.cs new file mode 100644 index 0000000..c8d864f --- /dev/null +++ b/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies.ScheduledTasks/TLTrailersScheduledTask.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Emby.Security; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Security; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Tasks; + +namespace MediaBrowser.Plugins.TimeLordMovies.ScheduledTasks; + +public class TLTrailersScheduledTask : IScheduledTask +{ + private readonly ILogger _logger; + + private readonly ILibraryManager _libraryManager; + + private readonly IMediaEncoder _mediaEncoder; + + private readonly IFileSystem _fileSystem; + + private readonly IApplicationPaths _appPaths; + + private readonly ILibraryMonitor _libraryMonitor; + + private readonly IUserManager _userManager; + + private readonly INotificationManager _notificationManager; + + private readonly IItemRepository _itemRepo; + + public string Category => "Time Lord"; + + public string Key => "TimeLordMovies"; + + public string Description => "Sets the Trailer Date Added to the original premier date (Trailer)"; + + public string Name => "Change Trailer date added to original Premier date"; + + public TLTrailersScheduledTask(ILibraryManager libraryManager, ILogger logger, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IApplicationPaths appPaths, ILibraryMonitor libraryMonitor, ISecurityManager securityManager, IUserManager userManager, INotificationManager notificationManager, IItemRepository itemRepo) + { + _libraryManager = libraryManager; + _logger = logger; + _userManager = userManager; + _mediaEncoder = mediaEncoder; + _fileSystem = fileSystem; + _appPaths = appPaths; + _libraryMonitor = libraryMonitor; + _notificationManager = notificationManager; + _itemRepo = itemRepo; + } + + public async Task Execute(CancellationToken cancellationToken, IProgress progress) + { + RegRecord registration = await PluginSecurityManager.Current.GetRegistrationStatus("TimeLordMovies", cancellationToken).ConfigureAwait(continueOnCapturedContext: false); + Plugin.Instance.Registration = registration; + _logger.Info("TimeLordMovies Registration Status - Registered: {0} In trial: {2} Expiration Date: {1} Is Valid: {3}", new object[4] + { + Plugin.Instance.Registration.registered, + Plugin.Instance.Registration.expDate, + Plugin.Instance.Registration.isTrial, + Plugin.Instance.Registration.isValid + }); + if (Plugin.Instance.Registration.isTrial) + { + _logger.Info(((BasePlugin)Plugin.Instance).Name + " You are Using the Trial Version, Please Consider purchasing to continue using the plugin after the trial period", new object[0]); + } + if (!Plugin.Instance.Registration.isValid) + { + _logger.Info(((BasePlugin)Plugin.Instance).Name + " Trial Expired, Please register to continue using the plugin", new object[0]); + return; + } + _logger.Info("TimeLordMovies :: Start :", (object[])null); + try + { + _logger.Info("TimeLord :: Rework Trailer Dates :: Start", (object[])null); + AggregateFolder rootFolder = _libraryManager.RootFolder; + InternalItemsQuery val = new InternalItemsQuery(); + val.IncludeItemTypes = new string[1] { typeof(Trailer).Name }; + val.Recursive = true; + List list = ((Folder)rootFolder).GetItemList(val).Where(delegate(BaseItem i) + { + //IL_0001: Unknown result type (might be due to invalid IL or missing references) + if ((int)i.LocationType != 0) + { + DateTimeOffset dateCreated = i.DateCreated; + DateTimeOffset? premiereDate = i.PremiereDate; + if (premiereDate.HasValue && dateCreated > premiereDate.GetValueOrDefault() && i.PremiereDate.HasValue) + { + return !i.IsLocked; + } + } + return false; + }).ToList(); + int num = 0; + foreach (Trailer item in list) + { + Trailer val2 = item; + try + { + cancellationToken.ThrowIfCancellationRequested(); + ((BaseItem)val2).DateCreated = ((BaseItem)val2).PremiereDate.Value; + try + { + ((BaseItem)val2).UpdateToRepository((ItemUpdateType)16); + _logger.Info("TimeLord.Trailers :: Updated :: " + ((BaseItem)val2).Name, (object[])null); + } + catch + { + _logger.Info("TimeLord.Trailers :: Updated :: " + ((BaseItem)val2).Name, (object[])null); + } + } + catch (OperationCanceledException) + { + _logger.Info("TimeLord.Trailers :: Canceled", (object[])null); + break; + } + catch (Exception ex2) + { + _logger.ErrorException("TimeLord.Trailers :: Error messing about with {0}", ex2, new object[1] { ((BaseItem)val2).Name }); + } + num++; + double num2 = num; + num2 /= (double)list.Count(); + num2 *= 100.0; + progress.Report(num2); + } + } + catch (Exception ex3) + { + _logger.ErrorException("TimeLord.Trailers :: Error {0}", ex3, Array.Empty()); + } + } + + public IEnumerable GetDefaultTriggers() + { + //IL_0008: Unknown result type (might be due to invalid IL or missing references) + //IL_000d: Unknown result type (might be due to invalid IL or missing references) + //IL_0018: Unknown result type (might be due to invalid IL or missing references) + //IL_003a: Expected O, but got Unknown + return (IEnumerable)(object)new TaskTriggerInfo[1] + { + new TaskTriggerInfo + { + Type = "DailyTrigger", + TimeOfDayTicks = TimeSpan.FromHours(5.0).Ticks + } + }; + } +} diff --git a/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies.csproj b/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies.csproj new file mode 100644 index 0000000..2da21ec --- /dev/null +++ b/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies.csproj @@ -0,0 +1,23 @@ + + + MediaBrowser.Plugins.TimeLordMovies + False + netstandard2.0 + + + 12.0 + True + + + + + + + + + + + + + + \ No newline at end of file diff --git a/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies.thumb.png b/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies.thumb.png new file mode 100644 index 0000000..daf4a1d Binary files /dev/null and b/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies.thumb.png differ diff --git a/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies/Plugin.cs b/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies/Plugin.cs new file mode 100644 index 0000000..ad6b872 --- /dev/null +++ b/decompiled-TimeLordMovies/MediaBrowser.Plugins.TimeLordMovies/Plugin.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using Emby.Security; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Drawing; + +namespace MediaBrowser.Plugins.TimeLordMovies; + +public class Plugin : BasePlugin, IHasThumbImage +{ + private Guid _id = new Guid("71a3ff1b-c589-4a39-b948-12f08b7715ee"); + + public RegRecord Registration; + + public override string Name => "TimeLordMovies"; + + public override string Description => "TimeLord Movies allows you to set the date added for Movies to the original Theatrical release date set in the metadata for items in your collection. This is usefull when adding old Movies to your collection, as it prevents theses 'New' Movies from dominating your recently added list. Take control of time with TimeLord Movies."; + + public override Guid Id => _id; + + public ImageFormat ThumbImageFormat => (ImageFormat)3; + + public static Plugin Instance { get; private set; } + + public Stream GetThumbImage() + { + Type type = ((object)this).GetType(); + return type.Assembly.GetManifestResourceStream(type.Namespace + ".thumb.png"); + } + + public Plugin() + { + Instance = this; + } +} diff --git a/decompiled-TimeLordMovies/Properties/AssemblyInfo.cs b/decompiled-TimeLordMovies/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..150ff17 --- /dev/null +++ b/decompiled-TimeLordMovies/Properties/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +[assembly: ComVisible(false)] +[assembly: Guid("71a3ff1b-c589-4a39-b948-12f08b7715ee")] +[assembly: AssemblyCompany("ScoobydooProject.com")] +[assembly: AssemblyConfiguration("Release")] +[assembly: AssemblyCopyright("Copyright © 2019 ScoobydooProject.com")] +[assembly: AssemblyDescription("Wayback Machine for TV Movies")] +[assembly: AssemblyFileVersion("19.08.03.1105")] +[assembly: AssemblyInformationalVersion("3.2.32.14")] +[assembly: AssemblyProduct("MediaBrowser.Plugins.TimeLordMovies")] +[assembly: AssemblyTitle("MediaBrowser.Plugins.TimeLordMovies")] +[assembly: AssemblyVersion("19.8.3.1105")] diff --git a/plugin-release/NFOGuard.Emby.Plugin.dll b/plugin-release/NFOGuard.Emby.Plugin.dll new file mode 100644 index 0000000..2b50630 Binary files /dev/null and b/plugin-release/NFOGuard.Emby.Plugin.dll differ diff --git a/projectsummary.md b/projectsummary.md new file mode 100644 index 0000000..9f17a89 --- /dev/null +++ b/projectsummary.md @@ -0,0 +1,169 @@ +# NFOGuard Project Summary + +## Project Overview +NFOGuard is a comprehensive solution to protect and preserve date metadata for TV shows and movies across Plex, Emby, and Jellyfin media servers. The core issue is that these servers often display scan dates instead of the original import dates from NFO files. + +## Core Problem +- **TV episodes show scan dates instead of import dates in Emby** +- **Root Cause**: Emby displays `DateCreated` field, not NFO `` +- **Impact**: Users lose track of when content was actually imported/acquired + +## Project Components + +### 1. NFOGuard Core (Python) - v0.6.1 ✅ +**Status**: Completed and working +- Processes TV shows and movies +- Writes import date from NFO `` to `` field +- `` maps to Emby's `PremiereDate` field +- **Current Fix**: Writing import date to `` field (goes to PremiereDate) + +### 2. NFOGuard Emby Plugin (C# DLL) - v1.0.0 🚧 +**Status**: Built, ready for testing +- **Purpose**: Automatically sync `PremiereDate` → `DateCreated` in Emby +- **Approach**: Real-time processing via library events (not scheduled tasks) +- **Architecture**: Clean, standards-compliant Emby plugin + +## What We've Tried + +### ❌ Failed Approaches +1. **Direct NFO `` reading** - Emby ignores this field +2. **Waiting for auto-sync** - PremiereDate doesn't auto-sync to DateCreated +3. **Manual database updates** - Too complex, risky + +### ✅ Working Solutions +1. **NFOGuard Core**: Successfully writes to `` field +2. **Plugin approach**: Direct database updates via Emby API (`UpdateToRepository()`) + +## Current Implementation Status + +### NFOGuard Core Features ✅ +- TV show processing with configurable webhook modes +- Movie processing +- NFO date field manipulation +- Comprehensive logging and error handling +- CI/CD pipeline with automated testing + +### Emby Plugin Features ✅ - READY FOR TESTING +- **Real-time processing**: Library event hooks (`ItemAdded`/`ItemUpdated`) +- **Scheduled task mode**: Weekly batch processing of existing episodes +- **Flexible processing modes**: Real-time only, scheduled only, or both +- **Configuration UI**: Full settings page in Emby dashboard +- **TV episode detection**: Automatic filtering and processing +- **Core sync logic**: `PremiereDate` → `DateCreated` synchronization +- **Verbose logging**: Configurable detailed logging for troubleshooting +- **License framework**: Ready for future licensing (currently disabled) +- **Standard architecture**: Follows official Emby plugin guidelines + +## Technical Details + +### Key Discovery from TimeLord Analysis +```csharp +// Core sync operation (standard Emby API) +episode.DateCreated = episode.PremiereDate.Value; +episode.UpdateToRepository(ItemUpdateType.MetadataEdit); +``` + +### Our Implementation +- Uses standard Emby interfaces: `ILibraryManager`, `IItemRepository` +- Event-driven processing vs TimeLord's scheduled tasks +- Clean dependency injection and configuration management + +## Development Environment +- **Platform**: Windows with WSL (Ubuntu) +- **Build Tools**: .NET SDK 6.0 in WSL +- **Target Framework**: .NET Framework 4.8 +- **IDE**: VS Code in WSL +- **Emby Server**: Windows installation (can access DLLs directly) + +## Next Steps - Priority Order + +### Immediate (This Session) - ✅ COMPLETED +1. **✅ Create project summary** (this file) +2. **✅ Get Emby SDK DLLs** - From Windows Emby installation +3. **✅ Build plugin** - First compilation test SUCCESS! +4. **✅ Disable licensing** - For testing phase +5. **✅ Add settings UI** - Configuration page with options: + - ✅ Enable/disable real-time processing + - ✅ Enable/disable scheduled task mode + - ✅ Verbose logging toggle + - ✅ Processing mode selection (realtime/scheduled/both) +6. **✅ Add scheduled task** - Weekly batch processing option +7. **✅ Build final plugin** - All features integrated and working + +### Testing Phase - 🎉 MASSIVE SUCCESS! +1. **✅ Deploy to test Emby server** - PLUGIN WORKING PERFECTLY: + - **✅ PLUGIN LOADS**: Successfully loads and registers with Emby + - **🚀 SCHEDULED TASK SUCCESS**: Processed 15,039 episodes, updated 14,661 (97.5% success!) + - **⚡ BLAZING FAST**: 1,500 episodes per second processing speed + - **🆕 MOVIES SUPPORT**: Ready for movies (0 movies in test library) + - **✅ REAL-TIME + SCHEDULED**: Both modes working simultaneously + - **STATUS**: Core functionality 100% successful - ready for production! + - **Source**: `plugin-release/NFOGuard.Emby.Plugin.dll` (auto-copied after build) + - **Destination**: `C:\ProgramData\Emby-Server\plugins\NFOGuard.Emby.Plugin.dll` + - **Restart** Emby Server +2. **Configure plugin** - Dashboard → Plugins → NFOGuard → Settings +3. **Test with new TV episode import** +4. **Verify database sync**: `DateCreated` = `PremiereDate` +5. **Monitor logs for proper operation** + +### Production Features +8. **Add licensing system** - Custom server integration +9. **Performance optimization** +10. **Error handling improvements** +11. **Documentation and user guides** + +## File Structure +``` +NFOguard/ +├── projectsummary.md (this file) +├── README.md +├── VERSION +├── plugin-release/ ⭐ NEW - Auto-generated +│ └── NFOGuard.Emby.Plugin.dll (ready for deployment) +├── NFOGuard.Emby.Plugin/ +│ ├── Plugin.cs (main plugin class) +│ ├── PluginEntryPoint.cs (initialization) +│ ├── LibraryEventHandler.cs (core logic) +│ ├── NFOGuardScheduledTask.cs (batch processing) +│ ├── PluginConfigurationPage.cs (settings UI) +│ ├── Configuration/configPage.html (embedded UI) +│ ├── LicenseManager.cs (licensing framework) +│ ├── NFOGuard.Emby.Plugin.csproj +│ ├── build.sh / build.bat (auto-copies to plugin-release/) +│ ├── WSL_SETUP.md +│ └── DEPLOYMENT.md +├── EmbySDK/ +│ ├── MediaBrowser.Common.dll +│ ├── MediaBrowser.Controller.dll +│ └── MediaBrowser.Model.dll +└── decompiled-TimeLord/ (reference only) +``` + +## Current Branch Status +- **Branch**: `dev` +- **Main branch**: `main` (for PRs) +- **Modified files**: `README.md`, `VERSION` +- **New files**: Complete Emby plugin structure + +## Success Criteria +1. **Core functionality**: TV episodes show import dates, not scan dates +2. **User experience**: Seamless, automatic operation +3. **Reliability**: Handles errors gracefully, doesn't break Emby +4. **Performance**: Minimal impact on library operations +5. **Maintainability**: Clean, well-documented code + +## Known Limitations & Future Considerations +- **Movies**: May need separate handling if date sync issues occur +- **Bulk processing**: Currently real-time only, may need batch mode +- **Multi-server**: Plugin needs to be installed per Emby instance +- **Backup compatibility**: Ensure database changes don't break backups + +## Key Learnings +- **Emby architecture**: Library events are reliable for real-time processing +- **Database operations**: `UpdateToRepository()` is the proper way to persist changes +- **Plugin standards**: Modern SDK-style projects work better in WSL +- **TimeLord insights**: Custom licensing systems are common but not required + +--- +*Last Updated: 2025-01-12* +*Next Session: Get SDK DLLs, build plugin, add settings UI* \ No newline at end of file