From b50f78e5da6f3fdfc59e577ca61b88771da7d211 Mon Sep 17 00:00:00 2001 From: LukePulverenti Luke Pulverenti luke pulverenti Date: Thu, 12 Jul 2012 02:55:27 -0400 Subject: Initial check-in --- MediaBrowser.Controller/IO/DirectoryWatchers.cs | 152 ++++++++++++++++++++ MediaBrowser.Controller/IO/Shortcut.cs | 182 ++++++++++++++++++++++++ 2 files changed, 334 insertions(+) create mode 100644 MediaBrowser.Controller/IO/DirectoryWatchers.cs create mode 100644 MediaBrowser.Controller/IO/Shortcut.cs (limited to 'MediaBrowser.Controller/IO') diff --git a/MediaBrowser.Controller/IO/DirectoryWatchers.cs b/MediaBrowser.Controller/IO/DirectoryWatchers.cs new file mode 100644 index 000000000..dd2769583 --- /dev/null +++ b/MediaBrowser.Controller/IO/DirectoryWatchers.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.IO +{ + public class DirectoryWatchers + { + private List FileSystemWatchers = new List(); + private Timer updateTimer = null; + private List affectedPaths = new List(); + + private const int TimerDelayInSeconds = 5; + + public void Start() + { + List pathsToWatch = new List(); + + var rootFolder = Kernel.Instance.RootFolder; + + pathsToWatch.Add(rootFolder.Path); + + foreach (Folder folder in rootFolder.FolderChildren) + { + foreach (Folder subFolder in folder.FolderChildren) + { + if (Path.IsPathRooted(subFolder.Path)) + { + string parent = Path.GetDirectoryName(subFolder.Path); + + if (!pathsToWatch.Contains(parent)) + { + pathsToWatch.Add(parent); + } + } + } + } + + foreach (string path in pathsToWatch) + { + FileSystemWatcher watcher = new FileSystemWatcher(path, "*"); + + watcher.IncludeSubdirectories = true; + + watcher.Changed += watcher_Changed; + + // All the others seem to trigger change events on the parent, so let's keep it simple for now. + //watcher.Created += watcher_Changed; + //watcher.Deleted += watcher_Changed; + //watcher.Renamed += watcher_Changed; + + watcher.EnableRaisingEvents = true; + FileSystemWatchers.Add(watcher); + } + } + + void watcher_Changed(object sender, FileSystemEventArgs e) + { + if (!affectedPaths.Contains(e.FullPath)) + { + affectedPaths.Add(e.FullPath); + } + + if (updateTimer == null) + { + updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(TimerDelayInSeconds), TimeSpan.FromMilliseconds(-1)); + } + else + { + updateTimer.Change(TimeSpan.FromSeconds(TimerDelayInSeconds), TimeSpan.FromMilliseconds(-1)); + } + } + + private void TimerStopped(object stateInfo) + { + updateTimer.Dispose(); + updateTimer = null; + + List paths = affectedPaths; + affectedPaths = new List(); + + ProcessPathChanges(paths); + } + + private void ProcessPathChanges(IEnumerable paths) + { + List itemsToRefresh = new List(); + + foreach (BaseItem item in paths.Select(p => GetAffectedBaseItem(p))) + { + if (item != null && !itemsToRefresh.Contains(item)) + { + itemsToRefresh.Add(item); + } + } + + if (itemsToRefresh.Any(i => + { + var folder = i as Folder; + + return folder != null && folder.IsRoot; + })) + { + Kernel.Instance.ReloadRoot(); + } + else + { + Parallel.For(0, itemsToRefresh.Count, i => + { + Kernel.Instance.ReloadItem(itemsToRefresh[i]); + }); + } + } + + private BaseItem GetAffectedBaseItem(string path) + { + BaseItem item = null; + + while (item == null) + { + item = Kernel.Instance.RootFolder.FindByPath(path); + + path = Path.GetDirectoryName(path); + } + + return item; + } + + public void Stop() + { + foreach (FileSystemWatcher watcher in FileSystemWatchers) + { + watcher.Changed -= watcher_Changed; + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + } + + if (updateTimer != null) + { + updateTimer.Dispose(); + updateTimer = null; + } + + FileSystemWatchers.Clear(); + affectedPaths.Clear(); + } + } +} diff --git a/MediaBrowser.Controller/IO/Shortcut.cs b/MediaBrowser.Controller/IO/Shortcut.cs new file mode 100644 index 000000000..376d16a79 --- /dev/null +++ b/MediaBrowser.Controller/IO/Shortcut.cs @@ -0,0 +1,182 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace MediaBrowser.Controller.IO +{ + public static class Shortcut + { + #region Signitures were imported from http://pinvoke.net + [Flags()] + enum SLGP_FLAGS + { + /// Retrieves the standard short (8.3 format) file name + SLGP_SHORTPATH = 0x1, + /// Retrieves the Universal Naming Convention (UNC) path name of the file + SLGP_UNCPRIORITY = 0x2, + /// Retrieves the raw path name. A raw path is something that might not exist and may include environment variables that need to be expanded + SLGP_RAWPATH = 0x4 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + struct WIN32_FIND_DATAW + { + public uint dwFileAttributes; + public long ftCreationTime; + public long ftLastAccessTime; + public long ftLastWriteTime; + public uint nFileSizeHigh; + public uint nFileSizeLow; + public uint dwReserved0; + public uint dwReserved1; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string cFileName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] + public string cAlternateFileName; + } + + [Flags()] + + enum SLR_FLAGS + { + /// + /// Do not display a dialog box if the link cannot be resolved. When SLR_NO_UI is set, + /// the high-order word of fFlags can be set to a time-out value that specifies the + /// maximum amount of time to be spent resolving the link. The function returns if the + /// link cannot be resolved within the time-out duration. If the high-order word is set + /// to zero, the time-out duration will be set to the default value of 3,000 milliseconds + /// (3 seconds). To specify a value, set the high word of fFlags to the desired time-out + /// duration, in milliseconds. + /// + SLR_NO_UI = 0x1, + /// Obsolete and no longer used + SLR_ANY_MATCH = 0x2, + /// If the link object has changed, update its path and list of identifiers. + /// If SLR_UPDATE is set, you do not need to call IPersistFile::IsDirty to determine + /// whether or not the link object has changed. + SLR_UPDATE = 0x4, + /// Do not update the link information + SLR_NOUPDATE = 0x8, + /// Do not execute the search heuristics + SLR_NOSEARCH = 0x10, + /// Do not use distributed link tracking + SLR_NOTRACK = 0x20, + /// Disable distributed link tracking. By default, distributed link tracking tracks + /// removable media across multiple devices based on the volume name. It also uses the + /// Universal Naming Convention (UNC) path to track remote file systems whose drive letter + /// has changed. Setting SLR_NOLINKINFO disables both types of tracking. + SLR_NOLINKINFO = 0x40, + /// Call the Microsoft Windows Installer + SLR_INVOKE_MSI = 0x80 + } + + + /// The IShellLink interface allows Shell links to be created, modified, and resolved + [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F9-0000-0000-C000-000000000046")] + interface IShellLinkW + { + /// Retrieves the path and file name of a Shell link object + void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags); + /// Retrieves the list of item identifiers for a Shell link object + void GetIDList(out IntPtr ppidl); + /// Sets the pointer to an item identifier list (PIDL) for a Shell link object. + void SetIDList(IntPtr pidl); + /// Retrieves the description string for a Shell link object + void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName); + /// Sets the description for a Shell link object. The description can be any application-defined string + void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); + /// Retrieves the name of the working directory for a Shell link object + void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath); + /// Sets the name of the working directory for a Shell link object + void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); + /// Retrieves the command-line arguments associated with a Shell link object + void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath); + /// Sets the command-line arguments for a Shell link object + void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); + /// Retrieves the hot key for a Shell link object + void GetHotkey(out short pwHotkey); + /// Sets a hot key for a Shell link object + void SetHotkey(short wHotkey); + /// Retrieves the show command for a Shell link object + void GetShowCmd(out int piShowCmd); + /// Sets the show command for a Shell link object. The show command sets the initial show state of the window. + void SetShowCmd(int iShowCmd); + /// Retrieves the location (path and index) of the icon for a Shell link object + void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, + int cchIconPath, out int piIcon); + /// Sets the location (path and index) of the icon for a Shell link object + void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); + /// Sets the relative path to the Shell link object + void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved); + /// Attempts to find the target of a Shell link, even if it has been moved or renamed + void Resolve(IntPtr hwnd, SLR_FLAGS fFlags); + /// Sets the path and file name of a Shell link object + void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); + + } + + [ComImport, Guid("0000010c-0000-0000-c000-000000000046"), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IPersist + { + [PreserveSig] + void GetClassID(out Guid pClassID); + } + + + [ComImport, Guid("0000010b-0000-0000-C000-000000000046"), + InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IPersistFile : IPersist + { + new void GetClassID(out Guid pClassID); + [PreserveSig] + int IsDirty(); + + [PreserveSig] + void Load([In, MarshalAs(UnmanagedType.LPWStr)] + string pszFileName, uint dwMode); + + [PreserveSig] + void Save([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName, + [In, MarshalAs(UnmanagedType.Bool)] bool remember); + + [PreserveSig] + void SaveCompleted([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName); + + [PreserveSig] + void GetCurFile([In, MarshalAs(UnmanagedType.LPWStr)] string ppszFileName); + } + + const uint STGM_READ = 0; + const int MAX_PATH = 260; + + // CLSID_ShellLink from ShlGuid.h + [ + ComImport(), + Guid("00021401-0000-0000-C000-000000000046") + ] + public class ShellLink + { + } + + #endregion + + public static string ResolveShortcut(string filename) + { + ShellLink link = new ShellLink(); + ((IPersistFile)link).Load(filename, STGM_READ); + // TODO: if I can get hold of the hwnd call resolve first. This handles moved and renamed files. + // ((IShellLinkW)link).Resolve(hwnd, 0) + StringBuilder sb = new StringBuilder(MAX_PATH); + WIN32_FIND_DATAW data = new WIN32_FIND_DATAW(); + ((IShellLinkW)link).GetPath(sb, sb.Capacity, out data, 0); + return sb.ToString(); + } + + public static bool IsShortcut(string filename) + { + return Path.GetExtension(filename).EndsWith("lnk", StringComparison.OrdinalIgnoreCase); + } + } +} -- cgit v1.2.3