using MicaSetup.Helper; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; namespace MicaSetup.Shell.Dialogs; #pragma warning disable CS8601 #pragma warning disable CS8618 #pragma warning disable IDE0059 public sealed class ShellLibrary : ShellContainer, IList { internal const string FileExtension = ".library-ms"; private static readonly Guid[] FolderTypesGuids = { new Guid(ShellKFIDGuid.GenericLibrary), new Guid(ShellKFIDGuid.DocumentsLibrary), new Guid(ShellKFIDGuid.MusicLibrary), new Guid(ShellKFIDGuid.PicturesLibrary), new Guid(ShellKFIDGuid.VideosLibrary) }; private readonly IKnownFolder knownFolder; private INativeShellLibrary nativeShellLibrary; public ShellLibrary(string libraryName, bool overwrite) : this() { if (string.IsNullOrEmpty(libraryName)) { throw new ArgumentException(LocalizedMessages.ShellLibraryEmptyName, "libraryName"); } Name = libraryName; var guid = new Guid(ShellKFIDGuid.Libraries); var flags = overwrite ? LibrarySaveOptions.OverrideExisting : LibrarySaveOptions.FailIfThere; nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); nativeShellLibrary.SaveInKnownFolder(ref guid, libraryName, flags, out nativeShellItem); } public ShellLibrary(string libraryName, IKnownFolder sourceKnownFolder, bool overwrite) : this() { if (string.IsNullOrEmpty(libraryName)) { throw new ArgumentException(LocalizedMessages.ShellLibraryEmptyName, "libraryName"); } knownFolder = sourceKnownFolder; Name = libraryName; var guid = knownFolder.FolderId; var flags = overwrite ? LibrarySaveOptions.OverrideExisting : LibrarySaveOptions.FailIfThere; nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); nativeShellLibrary.SaveInKnownFolder(ref guid, libraryName, flags, out nativeShellItem); } public ShellLibrary(string libraryName, string folderPath, bool overwrite) : this() { if (string.IsNullOrEmpty(libraryName)) { throw new ArgumentException(LocalizedMessages.ShellLibraryEmptyName, "libraryName"); } if (!Directory.Exists(folderPath)) { throw new DirectoryNotFoundException(LocalizedMessages.ShellLibraryFolderNotFound); } Name = libraryName; var flags = overwrite ? LibrarySaveOptions.OverrideExisting : LibrarySaveOptions.FailIfThere; var guid = new Guid(ShellIIDGuid.IShellItem); Shell32.SHCreateItemFromParsingName(folderPath, 0, ref guid, out IShellItem shellItemIn); nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); nativeShellLibrary.Save(shellItemIn, libraryName, flags, out nativeShellItem); } private ShellLibrary() => OsVersionHelper.ThrowIfNotWin7(); private ShellLibrary(INativeShellLibrary nativeShellLibrary) : this() => this.nativeShellLibrary = nativeShellLibrary; private ShellLibrary(IKnownFolder sourceKnownFolder, bool isReadOnly) : this() { Debug.Assert(sourceKnownFolder != null); knownFolder = sourceKnownFolder; nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); var flags = isReadOnly ? AccessModes.Read : AccessModes.ReadWrite; base.nativeShellItem = ((ShellObject)sourceKnownFolder!).NativeShellItem2; var guid = sourceKnownFolder.FolderId; try { nativeShellLibrary.LoadLibraryFromKnownFolder(ref guid, flags); } catch (InvalidCastException) { throw new ArgumentException(LocalizedMessages.ShellLibraryInvalidLibrary, "sourceKnownFolder"); } catch (NotImplementedException) { throw new ArgumentException(LocalizedMessages.ShellLibraryInvalidLibrary, "sourceKnownFolder"); } } ~ShellLibrary() { Dispose(false); } public new static bool IsPlatformSupported => OsVersionHelper.IsWindows7_OrGreater; public static IKnownFolder LibrariesKnownFolder { get { OsVersionHelper.ThrowIfNotWin7(); return KnownFolderHelper.FromKnownFolderId(new Guid(ShellKFIDGuid.Libraries)); } } public string DefaultSaveFolder { get { var guid = new Guid(ShellIIDGuid.IShellItem); nativeShellLibrary.GetDefaultSaveFolder( DefaultSaveFolderType.Detect, ref guid, out var saveFolderItem); return ShellHelper.GetParsingName(saveFolderItem); } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentNullException("value"); } if (!Directory.Exists(value)) { throw new DirectoryNotFoundException(LocalizedMessages.ShellLibraryDefaultSaveFolderNotFound); } var fullPath = new DirectoryInfo(value).FullName; var guid = new Guid(ShellIIDGuid.IShellItem); Shell32.SHCreateItemFromParsingName(fullPath, 0, ref guid, out IShellItem saveFolderItem); nativeShellLibrary.SetDefaultSaveFolder( DefaultSaveFolderType.Detect, saveFolderItem); nativeShellLibrary.Commit(); } } public IconReference IconResourceId { get { nativeShellLibrary.GetIcon(out var iconRef); return new IconReference(iconRef); } set { nativeShellLibrary.SetIcon(value.ReferencePath); nativeShellLibrary.Commit(); } } public bool IsPinnedToNavigationPane { get { nativeShellLibrary.GetOptions(out LibraryOptions flags); return ( (flags & LibraryOptions.PinnedToNavigationPane) == LibraryOptions.PinnedToNavigationPane); } set { var flags = LibraryOptions.Default; if (value) { flags |= LibraryOptions.PinnedToNavigationPane; } else { flags &= ~LibraryOptions.PinnedToNavigationPane; } nativeShellLibrary.SetOptions(LibraryOptions.PinnedToNavigationPane, flags); nativeShellLibrary.Commit(); } } public bool IsReadOnly => false; public LibraryFolderType LibraryType { get { nativeShellLibrary.GetFolderType(out var folderTypeGuid); return GetFolderTypefromGuid(folderTypeGuid); } set { var guid = FolderTypesGuids[(int)value]; nativeShellLibrary.SetFolderType(ref guid); nativeShellLibrary.Commit(); } } public Guid LibraryTypeId { get { nativeShellLibrary.GetFolderType(out var folderTypeGuid); return folderTypeGuid; } } public override string Name { get { if (base.Name == null && NativeShellItem != null) { base.Name = System.IO.Path.GetFileNameWithoutExtension(ShellHelper.GetParsingName(NativeShellItem)); } return base.Name!; } } public int Count => ItemsList.Count; internal override IShellItem NativeShellItem => NativeShellItem2; internal override IShellItem2 NativeShellItem2 => nativeShellItem; private List ItemsList => GetFolders(); public ShellFileSystemFolder this[int index] { get => ItemsList[index]; set => throw new NotImplementedException(); } public static ShellLibrary Load(string libraryName, bool isReadOnly) { OsVersionHelper.ThrowIfNotWin7(); var kf = KnownFolders.Libraries; var librariesFolderPath = (kf != null) ? kf.Path : string.Empty; var guid = new Guid(ShellIIDGuid.IShellItem); var shellItemPath = System.IO.Path.Combine(librariesFolderPath, libraryName + FileExtension); var hr = Shell32.SHCreateItemFromParsingName(shellItemPath, 0, ref guid, out IShellItem nativeShellItem); if (!CoreErrorHelper.Succeeded(hr)) throw new ShellException(hr); var nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); var flags = isReadOnly ? AccessModes.Read : AccessModes.ReadWrite; nativeShellLibrary.LoadLibraryFromItem(nativeShellItem, flags); var library = new ShellLibrary(nativeShellLibrary); try { library.nativeShellItem = (IShellItem2)nativeShellItem; library.Name = libraryName; return library; } catch { library.Dispose(); throw; } } public static ShellLibrary Load(string libraryName, string folderPath, bool isReadOnly) { OsVersionHelper.ThrowIfNotWin7(); var shellItemPath = System.IO.Path.Combine(folderPath, libraryName + FileExtension); var item = ShellFile.FromFilePath(shellItemPath); var nativeShellItem = item.NativeShellItem; var nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); var flags = isReadOnly ? AccessModes.Read : AccessModes.ReadWrite; nativeShellLibrary.LoadLibraryFromItem(nativeShellItem, flags); var library = new ShellLibrary(nativeShellLibrary); try { library.nativeShellItem = (IShellItem2)nativeShellItem; library.Name = libraryName; return library; } catch { library.Dispose(); throw; } } public static ShellLibrary Load(IKnownFolder sourceKnownFolder, bool isReadOnly) { OsVersionHelper.ThrowIfNotWin7(); return new ShellLibrary(sourceKnownFolder, isReadOnly); } public static void ShowManageLibraryUI(string libraryName, string folderPath, nint windowHandle, string title, string instruction, bool allowAllLocations) { using ShellLibrary shellLibrary = ShellLibrary.Load(libraryName, folderPath, true); ShowManageLibraryUI(shellLibrary, windowHandle, title, instruction, allowAllLocations); } public static void ShowManageLibraryUI(string libraryName, nint windowHandle, string title, string instruction, bool allowAllLocations) { using ShellLibrary shellLibrary = ShellLibrary.Load(libraryName, true); ShowManageLibraryUI(shellLibrary, windowHandle, title, instruction, allowAllLocations); } public static void ShowManageLibraryUI(IKnownFolder sourceKnownFolder, nint windowHandle, string title, string instruction, bool allowAllLocations) { using ShellLibrary shellLibrary = Load(sourceKnownFolder, true); ShowManageLibraryUI(shellLibrary, windowHandle, title, instruction, allowAllLocations); } public void Add(ShellFileSystemFolder item) { if (item == null!) { throw new ArgumentNullException("item"); } nativeShellLibrary.AddFolder(item.NativeShellItem); nativeShellLibrary.Commit(); } public void Add(string folderPath) { if (!Directory.Exists(folderPath)) { throw new DirectoryNotFoundException(LocalizedMessages.ShellLibraryFolderNotFound); } Add(ShellFileSystemFolder.FromFolderPath(folderPath)); } public void Clear() { var list = ItemsList; foreach (var folder in list) { nativeShellLibrary.RemoveFolder(folder.NativeShellItem); } nativeShellLibrary.Commit(); } public void Close() => Dispose(); public bool Contains(string fullPath) { if (string.IsNullOrEmpty(fullPath)) { throw new ArgumentNullException("fullPath"); } return ItemsList.Any(folder => string.Equals(fullPath, folder.Path, StringComparison.OrdinalIgnoreCase)); } public bool Contains(ShellFileSystemFolder item) { if (item == null!) { throw new ArgumentNullException("item"); } return ItemsList.Any(folder => string.Equals(item.Path, folder.Path, StringComparison.OrdinalIgnoreCase)); } public new IEnumerator GetEnumerator() => ItemsList.GetEnumerator(); public int IndexOf(ShellFileSystemFolder item) => ItemsList.IndexOf(item); public bool Remove(ShellFileSystemFolder item) { if (item == null!) { throw new ArgumentNullException("item"); } try { nativeShellLibrary.RemoveFolder(item.NativeShellItem); nativeShellLibrary.Commit(); } catch (COMException) { return false; } return true; } public bool Remove(string folderPath) { var item = ShellFileSystemFolder.FromFolderPath(folderPath); return Remove(item); } void ICollection.CopyTo(ShellFileSystemFolder[] array, int arrayIndex) => throw new NotImplementedException(); System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => ItemsList.GetEnumerator(); void IList.Insert(int index, ShellFileSystemFolder item) => throw new NotImplementedException(); void IList.RemoveAt(int index) => throw new NotImplementedException(); internal static ShellLibrary FromShellItem(IShellItem nativeShellItem, bool isReadOnly) { OsVersionHelper.ThrowIfNotWin7(); var nativeShellLibrary = (INativeShellLibrary)new ShellLibraryCoClass(); var flags = isReadOnly ? AccessModes.Read : AccessModes.ReadWrite; nativeShellLibrary.LoadLibraryFromItem(nativeShellItem, flags); ShellLibrary library = new(nativeShellLibrary) { nativeShellItem = (IShellItem2)nativeShellItem }; return library; } protected override void Dispose(bool disposing) { if (nativeShellLibrary != null) { Marshal.ReleaseComObject(nativeShellLibrary); nativeShellLibrary = null!; } base.Dispose(disposing); } private static LibraryFolderType GetFolderTypefromGuid(Guid folderTypeGuid) { for (var i = 0; i < FolderTypesGuids.Length; i++) { if (folderTypeGuid.Equals(FolderTypesGuids[i])) { return (LibraryFolderType)i; } } throw new ArgumentOutOfRangeException("folderTypeGuid", LocalizedMessages.ShellLibraryInvalidFolderType); } private static void ShowManageLibraryUI(ShellLibrary shellLibrary, nint windowHandle, string title, string instruction, bool allowAllLocations) { var hr = 0; var staWorker = new Thread(() => { hr = Shell32.SHShowManageLibraryUI( shellLibrary.NativeShellItem, windowHandle, title, instruction, allowAllLocations ? LibraryManageDialogOptions.NonIndexableLocationWarning : LibraryManageDialogOptions.Default); }); staWorker.SetApartmentState(ApartmentState.STA); staWorker.Start(); staWorker.Join(); if (!CoreErrorHelper.Succeeded(hr)) { throw new ShellException(hr); } } private List GetFolders() { var list = new List(); var shellItemArrayGuid = new Guid(ShellIIDGuid.IShellItemArray); var hr = nativeShellLibrary.GetFolders(LibraryFolderFilter.AllItems, ref shellItemArrayGuid, out var itemArray); if (!CoreErrorHelper.Succeeded(hr)) { return list; } itemArray.GetCount(out var count); for (uint i = 0; i < count; ++i) { itemArray.GetItemAt(i, out var shellItem); list.Add(new ShellFileSystemFolder((shellItem as IShellItem2)!)); } if (itemArray != null) { Marshal.ReleaseComObject(itemArray); itemArray = null; } return list; } }