using System; using System.ComponentModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Diagnostics; using OwlCore; using OwlCore.AbstractStorage; using OwlCore.AbstractUI.Models; using OwlCore.Extensions; using StrixMusic.Cores.Files; using StrixMusic.Cores.Files.Models; using StrixMusic.Cores.LocalFiles.Services; using StrixMusic.Sdk.AppModels; using StrixMusic.Sdk.CoreModels; using StrixMusic.Sdk.FileMetadata; using StrixMusic.Sdk.MediaPlayback; using StrixMusic.Sdk.Services; namespace StrixMusic.Cores.LocalFiles { /// public sealed class LocalFilesCore : FilesCore { private Notification? _scannerRequiredNotification; private readonly LocalFilesCoreConfigPanel _configPanel = new(); /// /// Initializes a new instance of the class. /// /// A unique identifier for this core instance. /// An abstraction of the local file system. /// A folder abstraction where this core can persist settings data beyond the lifetime of the application. /// A service that can notify the user with interactive UI or messages. public LocalFilesCore(string instanceId, IFileSystemService fileSystem, IFolderData settingsStorage, INotificationService notificationService) : base(instanceId) { FileSystem = fileSystem; NotificationService = notificationService; Settings = new LocalFilesCoreSettings(settingsStorage); AttachEvents(); } /// /// Initializes a new instance of the class. /// /// /// This overload allows passing preconfigured settings that, if all values are valid, will allow initialization to complete without /// any interaction from the user. /// /// A unique identifier for this core instance. /// A preconfigured instance of that will be used instead of a new instance with default values. /// An abstraction of the local file system. /// A service that can notify the user with interactive UI or messages. public LocalFilesCore(string instanceId, LocalFilesCoreSettings settings, IFileSystemService fileSystem, INotificationService notificationService) : base(instanceId) { FileSystem = fileSystem; NotificationService = notificationService; Settings = settings; AttachEvents(); } private void AttachEvents() { Settings.PropertyChanged += OnSettingChanged; _configPanel.RescanButton.Clicked += RescanButton_Clicked; _configPanel.UseFilePropsScannerToggle.StateChanged += UseFilePropsScannerToggle_StateChanged; _configPanel.UseTagLibScannerToggle.StateChanged += UseTagLibScannerToggle_StateChanged; } private void DetachEvents() { Settings.PropertyChanged -= OnSettingChanged; _configPanel.RescanButton.Clicked -= RescanButton_Clicked; _configPanel.UseFilePropsScannerToggle.StateChanged -= UseFilePropsScannerToggle_StateChanged; _configPanel.UseTagLibScannerToggle.StateChanged -= UseTagLibScannerToggle_StateChanged; } /// public override CoreMetadata Registration { get; } = Metadata; /// public override AbstractUICollection AbstractConfigPanel => _configPanel; /// public override MediaPlayerType PlaybackType { get; } = MediaPlayerType.Standard; /// /// The metadata that identifies this core before instantiation. /// public static CoreMetadata Metadata { get; } = new CoreMetadata(id: nameof(LocalFilesCore), displayName: "Local Files", logoUri: new Uri("ms-appx:///Assets/Cores/LocalFiles/Logo.svg"), sdkVer: Version.Parse("0.0.0.0")); /// /// The settings for this core instance. /// internal LocalFilesCoreSettings Settings { get; } /// /// An abstraction of the local file system. /// internal IFileSystemService FileSystem { get; } /// /// Gets a service that can notify the user with interactive UI or generic messages. /// internal INotificationService NotificationService { get; } /// /// Change the . /// /// The new state. public void ChangeCoreState(CoreState state) { CoreState = state; CoreStateChanged?.Invoke(this, state); } /// public override ValueTask DisposeAsync() { // Dispose any resources not known to the SDK. // Do not dispose Library, Devices, etc. manually. The SDK will dispose these for you. FileMetadataManager?.DisposeAsync(); _configPanel.ConfigDoneButton.Clicked -= ConfigDoneButtonOnClicked; DetachEvents(); return default; } /// public override event EventHandler? CoreStateChanged; /// public override event EventHandler? AbstractConfigPanelChanged; /// public override event EventHandler? InstanceDescriptorChanged; /// public async override Task InitAsync(CancellationToken cancellationToken = default) { ChangeCoreState(CoreState.Loading); await Settings.LoadAsync(cancellationToken); await FileSystem.InitAsync(cancellationToken); cancellationToken.ThrowIfCancellationRequested(); GetConfiguredFolder: var configuredFolder = string.IsNullOrWhiteSpace(Settings.FolderPath) ? null : await FileSystem.GetFolderFromPathAsync(Settings.FolderPath); cancellationToken.ThrowIfCancellationRequested(); if (configuredFolder is null) { // Let the user change settings for the selected folder before first scan. ChangeCoreState(CoreState.NeedsConfiguration); var pickedFolder = await FileSystem.PickFolder(); cancellationToken.ThrowIfCancellationRequested(); // No folder selected. if (pickedFolder is null) { ChangeCoreState(CoreState.Unloaded); throw new OperationCanceledException(); } Settings.FolderPath = pickedFolder.Path; _configPanel.Subtitle = pickedFolder.Path; AbstractConfigPanelChanged?.Invoke(this, EventArgs.Empty); await Flow.EventAsTask(x => _configPanel.ConfigDoneButton.Clicked += x, x => _configPanel.ConfigDoneButton.Clicked -= x, TimeSpan.FromMinutes(30)); goto GetConfiguredFolder; } ChangeCoreState(CoreState.Configured); ChangeCoreState(CoreState.Loading); _configPanel.Subtitle = configuredFolder.Path; AbstractConfigPanelChanged?.Invoke(this, EventArgs.Empty); // Load InstanceDescriptor = configuredFolder.Path; InstanceDescriptorChanged?.Invoke(this, InstanceDescriptor); FileMetadataManager = new FileMetadataManager(configuredFolder, FileSystem.RootFolder, NotificationService) { SkipRepoInit = Settings.InitWithEmptyMetadataRepos, ScanTypes = GetScanTypesFromSettings(), }; await FileMetadataManager.InitAsync(cancellationToken); _ = FileMetadataManager.ScanAsync(cancellationToken); await Library.Cast().InitAsync(cancellationToken); cancellationToken.ThrowIfCancellationRequested(); ChangeCoreState(CoreState.Loaded); _configPanel.ConfigDoneButton.Clicked += ConfigDoneButtonOnClicked; IsInitialized = true; } private void ConfigDoneButtonOnClicked(object sender, EventArgs e) { ChangeCoreState(CoreState.Configured); ChangeCoreState(CoreState.Loaded); } private MetadataScanTypes GetScanTypesFromSettings() { var scanTypes = MetadataScanTypes.None; if (Settings.ScanWithTagLib) scanTypes |= MetadataScanTypes.TagLib; if (Settings.ScanWithFileProperties) scanTypes |= MetadataScanTypes.FileProperties; return scanTypes; } private async void OnSettingChanged(object sender, PropertyChangedEventArgs e) { var isFilePropToggle = e.PropertyName == nameof(LocalFilesCoreSettings.ScanWithFileProperties); var isTagLibToggle = e.PropertyName == nameof(LocalFilesCoreSettings.ScanWithTagLib); var isAnyScannerToggle = isFilePropToggle || isTagLibToggle; if (isAnyScannerToggle && !Settings.ScanWithFileProperties && !Settings.ScanWithTagLib) { _scannerRequiredNotification?.Dismiss(); _scannerRequiredNotification = NotificationService.RaiseNotification("Whoops", "At least one metadata scanner is required."); if (isFilePropToggle) _configPanel.UseFilePropsScannerToggle.State = true; if (isTagLibToggle) _configPanel.UseTagLibScannerToggle.State = true; } else { _configPanel.UseFilePropsScannerToggle.State = Settings.ScanWithFileProperties; _configPanel.UseTagLibScannerToggle.State = Settings.ScanWithTagLib; _configPanel.InitWithEmptyReposToggle.State = Settings.InitWithEmptyMetadataRepos; } await Settings.SaveAsync(); } private void RescanButton_Clicked(object sender, EventArgs e) => _ = FileMetadataManager?.ScanAsync(); private void UseTagLibScannerToggle_StateChanged(object sender, bool e) { Settings.ScanWithTagLib = e; if (FileMetadataManager is null) return; // Enable or disable this scanner type. if (!FileMetadataManager.ScanTypes.HasFlag(MetadataScanTypes.TagLib) && e) FileMetadataManager.ScanTypes |= MetadataScanTypes.TagLib; else FileMetadataManager.ScanTypes ^= MetadataScanTypes.TagLib; } private void UseFilePropsScannerToggle_StateChanged(object sender, bool e) { Settings.ScanWithFileProperties = e; if (FileMetadataManager is null) return; // Enable or disable this scanner type. if (!FileMetadataManager.ScanTypes.HasFlag(MetadataScanTypes.FileProperties) && e) FileMetadataManager.ScanTypes |= MetadataScanTypes.FileProperties; else FileMetadataManager.ScanTypes ^= MetadataScanTypes.FileProperties; } } }