/// <summary> /// Synchronously scanns some FS folder, filling Items list of item passed /// </summary> /// <param name="item">A PowerItem that represents an FS folder, special folder or a Windows Library</param> /// <param name="basePath">If item passed represents folder under Start Menu, place here corresponding /// (Common or User) special folder path. Empty string otherwise. See AddSubItem for details.</param> /// <param name="recoursive">True to scan subdirectories.</param> private static void ScanFolderSync(PowerItem item, string basePath, bool recoursive) { //Obtain full fs path to current location var curDir = basePath + (item.Argument ?? Util.ResolveSpecialFolder(item.SpecialFolderId)); try { Log.Raw("In: " + curDir, item.ToString()); //Parse child directories and recoursively call the ScanFolderSync foreach (var directory in item.IsLibrary ? GetLibraryDirectories(curDir) : Directory.GetDirectories(curDir)) {//Skip hidden directories if ((File.GetAttributes(directory).HasFlag(FileAttributes.Hidden))) { Log.Raw("Skipped because item appears to be hidden"); continue; } //Otherwise add the directory item to this PowerItem var subitem = AddSubItem(item, basePath, directory, true, autoExpand: !recoursive); if (recoursive) ScanFolderSync(subitem, basePath, true); } if (item.IsLibrary)//Since Libraries are actually files, but were already parsed as folders, we shan't continue... return; //Proceed with files var resources = new Dictionary<string, string>(); var dsktp = curDir + "\\desktop.ini"; if (File.Exists(dsktp)) //Let's parse Desktop.ini if it exists { using (var reader = new StreamReader(dsktp, System.Text.Encoding.Default, true)) { string str; //TODO: rewrite!!! Currently elements located after LFN section aren't parsed! while ((str = reader.ReadLine()) != null && !str.Contains("[LocalizedFileNames]")) { if (str.StartsWith("IconFile=") || str.StartsWith("IconResource=")) { Util.Post(() => { item.NonCachedIcon = true; item.Icon = null; }); } if (str.StartsWith("LocalizedResourceName=")) { item.ResourceIdString = str.Substring(22); } } while ((str = reader.ReadLine()) != null && str.Contains("=")) { var pair = str.Split(new[] {'='}, 2); resources.Add(pair[0], pair[1]); } } } //Let's scan files now foreach (var file in Directory.GetFiles(curDir)) { if ((File.GetAttributes(file).HasFlag(FileAttributes.Hidden))) continue; //Skip hidden files var fn = Path.GetFileName(file); var fileIsLib = (Path.GetExtension(file) ?? "") .Equals(".library-ms", StringComparison.InvariantCultureIgnoreCase); AddSubItem(item, basePath, file, fileIsLib, fn != null && resources.ContainsKey(fn) ? resources[fn] : null, fileIsLib); } } catch (UnauthorizedAccessException) {Log.Raw("UnauthorizedAccessException");} //Don't care if user is not allowed to access fileor directory or it's contents catch (IOException) {Log.Raw("IOException");} //Don't care as well if file was deleted on-the-fly, watcher will notify list catch (ArgumentException) {Log.Raw("ArgumentException");} //This usually happens to removable drives finally { //Explicitly set marker showing that enumeration operations may occur on Items from this moment item.AutoExpandIsPending = false; } }
/// <summary> /// For the given parent PowerItem, searches child for given parameters, or creates a new one /// if search is unsuccessful or cannot be executed, and returns the child obtained. /// Thread-safe, UI-thread-safe. /// </summary> /// <param name="item">parent PowerItem, typically a filesystem folder, a Library or a special folder</param> /// <param name="basePath">if 'item' represents a folder under Start menu (and so has relative Argument), /// pass here a full-qualified path to a User or Common Start Menu (the one under which a folder is /// actually located). Use PathRoot and PathCommonRoot fields to simpolify the task. Empty string otherwise.</param> /// <param name="fsObject">Non-virtual non-junction file system object (file or folder), not null, not empty /// (this is the most meaningful parameter). If you need to add a virtual item (e.g. Computer element), use /// direct access to proprties (parent.Items.Add(child); child.Parent=parent;) with respect to /// IsAutoExpandPending property. Child search parameter.</param> /// <param name="isFolder">Sets child's IsFolder property to a value passed. Child search parameter.</param> /// <param name="resourceId">Localized resource identifier in a standard "[@]Library,-resId[__varData*]" form. /// Null by default. Since 0.4 can be set to a required string directly, but in this case it is recommended to /// set FriendlyName explicitly</param> /// <param name="autoExpand">True to mark the item as deferred-expandable. This means it's children won't be enumerated /// synchronously and will be loaded automatically later, when requested by the user. False by default.</param> private static PowerItem AddSubItem(PowerItem item, string basePath, string fsObject, bool isFolder, string resourceId = null, bool autoExpand = false) { var argStr = fsObject.Substring(basePath.Length); //Expected relative argument in case of Start Menu item Log.Raw("In: " + fsObject, item.ToString()); var child = autoExpand || item.AutoExpandIsPending //Searching... ? null : item.Items.FirstOrDefault(i => string.Equals(i.Argument, argStr, StringComparison.CurrentCultureIgnoreCase) && i.IsFolder == isFolder); Log.Raw("child: " + (child == null ? "null" : child.ToString()), item.ToString()); if(child == null) //Generating... { child = new PowerItem { Argument = argStr, Parent = item, IsFolder = isFolder, ResourceIdString = resourceId, AutoExpand = autoExpand }; Util.Send(() => item.Items.Add(child)); //Synchronously add item in UI thread } return child; }