using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.Logging; using MediaBrowser.Common.Serialization; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Tasks; using System; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Controller.Entities { /// /// Class User /// public class User : BaseItem { /// /// The _root folder path /// private string _rootFolderPath; /// /// Gets the root folder path. /// /// The root folder path. [IgnoreDataMember] public string RootFolderPath { get { if (_rootFolderPath == null) { if (Configuration.UseCustomLibrary) { _rootFolderPath = GetRootFolderPath(Name); if (!Directory.Exists(_rootFolderPath)) { Directory.CreateDirectory(_rootFolderPath); } } else { _rootFolderPath = Kernel.Instance.ApplicationPaths.DefaultUserViewsPath; } } return _rootFolderPath; } } /// /// Gets the root folder path based on a given username /// /// The username. /// System.String. private string GetRootFolderPath(string username) { var safeFolderName = FileSystem.GetValidFilename(username); return System.IO.Path.Combine(Kernel.Instance.ApplicationPaths.RootFolderPath, safeFolderName); } /// /// Gets or sets the password. /// /// The password. public string Password { get; set; } /// /// Gets or sets the path. /// /// The path. public override string Path { get { // Return this so that metadata providers will look in here return ConfigurationDirectoryPath; } set { base.Path = value; } } /// /// Ensure this has a value /// /// The display type of the media. public override string DisplayMediaType { get { return base.DisplayMediaType ?? GetType().Name; } set { base.DisplayMediaType = value; } } /// /// The _root folder /// private UserRootFolder _rootFolder; /// /// The _user root folder initialized /// private bool _userRootFolderInitialized; /// /// The _user root folder sync lock /// private object _userRootFolderSyncLock = new object(); /// /// Gets the root folder. /// /// The root folder. [IgnoreDataMember] public UserRootFolder RootFolder { get { LazyInitializer.EnsureInitialized(ref _rootFolder, ref _userRootFolderInitialized, ref _userRootFolderSyncLock, () => (UserRootFolder)Kernel.Instance.LibraryManager.GetItem(RootFolderPath)); return _rootFolder; } private set { _rootFolder = value; if (_rootFolder == null) { _userRootFolderInitialized = false; } } } /// /// Gets or sets the last login date. /// /// The last login date. public DateTime? LastLoginDate { get; set; } /// /// Gets or sets the last activity date. /// /// The last activity date. public DateTime? LastActivityDate { get; set; } /// /// The _configuration /// private UserConfiguration _configuration; /// /// The _configuration initialized /// private bool _configurationInitialized; /// /// The _configuration sync lock /// private object _configurationSyncLock = new object(); /// /// Gets the user's configuration /// /// The configuration. [IgnoreDataMember] public UserConfiguration Configuration { get { // Lazy load LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationInitialized, ref _configurationSyncLock, () => XmlSerializer.GetXmlConfiguration(ConfigurationFilePath)); return _configuration; } private set { _configuration = value; if (value == null) { _configurationInitialized = false; } } } /// /// Gets the last date modified of the configuration /// /// The configuration date last modified. [IgnoreDataMember] public DateTime ConfigurationDateLastModified { get { // Ensure it's been lazy loaded var config = Configuration; return File.GetLastWriteTimeUtc(ConfigurationFilePath); } } /// /// Reloads the root media folder /// /// The cancellation token. /// The progress. /// Task. public async Task ValidateMediaLibrary(IProgress progress, CancellationToken cancellationToken) { Logger.LogInfo("Validating media library for {0}", Name); await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); await RootFolder.ValidateChildren(progress, cancellationToken).ConfigureAwait(false); } /// /// Validates only the collection folders for a User and goes no further /// /// The cancellation token. /// The progress. /// Task. public async Task ValidateCollectionFolders(IProgress progress, CancellationToken cancellationToken) { Logger.LogInfo("Validating collection folders for {0}", Name); await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); await RootFolder.ValidateChildren(progress, cancellationToken, recursive: false).ConfigureAwait(false); } /// /// Renames the user. /// /// The new name. /// Task. /// internal Task Rename(string newName) { if (string.IsNullOrEmpty(newName)) { throw new ArgumentNullException(); } // If only the casing is changing, leave the file system alone if (!newName.Equals(Name, StringComparison.OrdinalIgnoreCase)) { // Move configuration var newConfigDirectory = GetConfigurationDirectoryPath(newName); // Exceptions will be thrown if these paths already exist if (Directory.Exists(newConfigDirectory)) { Directory.Delete(newConfigDirectory, true); } Directory.Move(ConfigurationDirectoryPath, newConfigDirectory); var customLibraryPath = GetRootFolderPath(Name); // Move the root folder path if using a custom library if (Directory.Exists(customLibraryPath)) { var newRootFolderPath = GetRootFolderPath(newName); if (Directory.Exists(newRootFolderPath)) { Directory.Delete(newRootFolderPath, true); } Directory.Move(customLibraryPath, newRootFolderPath); } } Name = newName; // Force these to be lazy loaded again _configurationDirectoryPath = null; _rootFolderPath = null; RootFolder = null; // Kick off a task to validate the media library Task.Run(() => ValidateMediaLibrary(new Progress { }, CancellationToken.None)); return RefreshMetadata(CancellationToken.None, forceSave: true, forceRefresh: true); } /// /// The _configuration directory path /// private string _configurationDirectoryPath; /// /// Gets the path to the user's configuration directory /// /// The configuration directory path. private string ConfigurationDirectoryPath { get { if (_configurationDirectoryPath == null) { _configurationDirectoryPath = GetConfigurationDirectoryPath(Name); if (!Directory.Exists(_configurationDirectoryPath)) { Directory.CreateDirectory(_configurationDirectoryPath); } } return _configurationDirectoryPath; } } /// /// Gets the configuration directory path. /// /// The username. /// System.String. private string GetConfigurationDirectoryPath(string username) { var safeFolderName = FileSystem.GetValidFilename(username); return System.IO.Path.Combine(Kernel.Instance.ApplicationPaths.UserConfigurationDirectoryPath, safeFolderName); } /// /// Gets the path to the user's configuration file /// /// The configuration file path. private string ConfigurationFilePath { get { return System.IO.Path.Combine(ConfigurationDirectoryPath, "config.xml"); } } /// /// Saves the current configuration to the file system /// public void SaveConfiguration() { XmlSerializer.SerializeToFile(Configuration, ConfigurationFilePath); } /// /// Refresh metadata on us by execution our provider chain /// The item will be persisted if a change is made by a provider, or if it's new or changed. /// /// The cancellation token. /// if set to true [is new item]. /// if set to true [force]. /// if set to true [allow slow providers]. /// if set to true [reset resolve args]. /// true if a provider reports we changed public override async Task RefreshMetadata(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true, bool resetResolveArgs = true) { if (resetResolveArgs) { ResolveArgs = null; } var changed = await Kernel.Instance.ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh, allowSlowProviders).ConfigureAwait(false); if (changed || forceSave) { cancellationToken.ThrowIfCancellationRequested(); await Kernel.Instance.UserManager.UpdateUser(this).ConfigureAwait(false); } return changed; } /// /// Updates the configuration. /// /// The config. /// config public void UpdateConfiguration(UserConfiguration config) { if (config == null) { throw new ArgumentNullException("config"); } var customLibraryChanged = config.UseCustomLibrary != Configuration.UseCustomLibrary; Configuration = config; SaveConfiguration(); // Force these to be lazy loaded again if (customLibraryChanged) { _rootFolderPath = null; RootFolder = null; if (config.UseCustomLibrary) { CopyDefaultLibraryPathsIfNeeded(); } } } /// /// Copies the default library paths if needed. /// private void CopyDefaultLibraryPathsIfNeeded() { var userPath = RootFolderPath; var defaultPath = Kernel.Instance.ApplicationPaths.DefaultUserViewsPath; if (userPath.Equals(defaultPath, StringComparison.OrdinalIgnoreCase)) { return; } if (!Directory.EnumerateFileSystemEntries(userPath, "*.lnk", SearchOption.AllDirectories).Any()) { FileSystem.CopyAll(defaultPath, userPath); } } /// /// Resets the password by clearing it. /// /// Task. public Task ResetPassword() { return ChangePassword(string.Empty); } /// /// Changes the password. /// /// The new password. /// Task. public Task ChangePassword(string newPassword) { Password = string.IsNullOrEmpty(newPassword) ? string.Empty : newPassword.GetMD5().ToString(); return Kernel.Instance.UserManager.UpdateUser(this); } } }