Browse Source

Avoiding use of shell API to query subfolders existence

(ntb, the shell call for roots/drives was noop as its results were never used)

Source commit: 08c4ed6e9e7a67fccff35045a7dcfb33b9941926
Martin Prikryl 1 year ago
parent
commit
13ad138a84
2 changed files with 35 additions and 198 deletions
  1. 33 150
      source/packages/filemng/DriveView.pas
  2. 2 48
      source/packages/my/OperationWithTimeout.pas

+ 33 - 150
source/packages/filemng/DriveView.pas

@@ -44,7 +44,7 @@ uses
   Windows, Messages, SysUtils, Classes,  Graphics, Controls, Forms, ComObj,
   Windows, Messages, SysUtils, Classes,  Graphics, Controls, Forms, ComObj,
   Dialogs, ComCtrls, ShellApi, CommCtrl, ExtCtrls, ActiveX,  ShlObj,
   Dialogs, ComCtrls, ShellApi, CommCtrl, ExtCtrls, ActiveX,  ShlObj,
   DirView, ShellDialogs, DragDrop, DragDropFilesEx, FileChanges, FileOperator,
   DirView, ShellDialogs, DragDrop, DragDropFilesEx, FileChanges, FileOperator,
-  DiscMon, IEDriveInfo, IEListView, PIDL, BaseUtils, CustomDirView,
+  DiscMon, IEDriveInfo, IEListView, BaseUtils, CustomDirView,
   CustomDriveView, System.Generics.Collections;
   CustomDriveView, System.Generics.Collections;
 
 
 const
 const
@@ -96,10 +96,6 @@ type
     FIconEmpty: Boolean;
     FIconEmpty: Boolean;
 
 
   public
   public
-    shAttr: ULONG;
-    PIDL: PItemIDList;
-    ShellFolder: IShellFolder;
-
     constructor Create;
     constructor Create;
     destructor Destroy; override;
     destructor Destroy; override;
 
 
@@ -141,8 +137,6 @@ type
     FPrevSelectedIndex: Integer;
     FPrevSelectedIndex: Integer;
     FChangeTimerSuspended: Integer;
     FChangeTimerSuspended: Integer;
 
 
-    FDesktop: IShellFolder;
-
     {Additional events:}
     {Additional events:}
     FOnDisplayContextMenu: TNotifyEvent;
     FOnDisplayContextMenu: TNotifyEvent;
     FOnRefreshDrives: TDriveViewRefreshDrives;
     FOnRefreshDrives: TDriveViewRefreshDrives;
@@ -161,7 +155,6 @@ type
     {Drag&drop helper functions:}
     {Drag&drop helper functions:}
     procedure SignalDirDelete(Sender: TObject; Files: TStringList);
     procedure SignalDirDelete(Sender: TObject; Files: TStringList);
 
 
-    function CheckForSubDirs(Path: string): Boolean;
     function GetSubDir(var SRec: TSearchRec): Boolean;
     function GetSubDir(var SRec: TSearchRec): Boolean;
     function FindFirstSubDir(Path: string; var SRec: TSearchRec): Boolean;
     function FindFirstSubDir(Path: string; var SRec: TSearchRec): Boolean;
     function FindNextSubDir(var SRec: TSearchRec): Boolean;
     function FindNextSubDir(var SRec: TSearchRec): Boolean;
@@ -185,9 +178,8 @@ type
     procedure SetShowVolLabel(ShowIt: Boolean);
     procedure SetShowVolLabel(ShowIt: Boolean);
     procedure SetDirView(Value: TDirView);
     procedure SetDirView(Value: TDirView);
     procedure SetDirectory(Value: string); override;
     procedure SetDirectory(Value: string); override;
-    procedure GetNodeShellAttr(ParentNode: TTreeNode; NodeData: TNodeData; GetAttr: Boolean);
     function  DoScanDir(FromNode: TTreeNode): Boolean;
     function  DoScanDir(FromNode: TTreeNode): Boolean;
-    function  AddChildNode(ParentNode: TTreeNode; SRec: TSearchRec): TTreeNode;
+    function  AddChildNode(ParentNode: TTreeNode; ParentPath: string; SRec: TSearchRec): TTreeNode;
     procedure CreateWatchThread(Drive: string);
     procedure CreateWatchThread(Drive: string);
     function NodeWatched(Node: TTreeNode): Boolean;
     function NodeWatched(Node: TTreeNode): Boolean;
     procedure TerminateWatchThread(Drive: string);
     procedure TerminateWatchThread(Drive: string);
@@ -410,7 +402,7 @@ procedure Register;
 implementation
 implementation
 
 
 uses
 uses
-  CompThread, PasTools, UITypes, Types, OperationWithTimeout, System.Generics.Defaults;
+  CompThread, PasTools, UITypes, Types, System.Generics.Defaults;
 
 
 type
 type
   PInt = ^Integer;
   PInt = ^Integer;
@@ -429,18 +421,11 @@ begin
   FDirName := '';
   FDirName := '';
   FIsRecycleBin := False;
   FIsRecycleBin := False;
   FIconEmpty := True;
   FIconEmpty := True;
-  shAttr := 0;
-  PIDL := nil;
-  ShellFolder := nil;
 end; {TNodeData.Create}
 end; {TNodeData.Create}
 
 
 destructor TNodeData.Destroy;
 destructor TNodeData.Destroy;
 begin
 begin
   SetLength(FDirName, 0);
   SetLength(FDirName, 0);
-
-  if Assigned(PIDL) then
-    FreePIDL(PIDL);
-
   inherited;
   inherited;
 end; {TNodeData.Destroy}
 end; {TNodeData.Destroy}
 
 
@@ -746,8 +731,6 @@ begin
   if Assigned(PopupMenu) then
   if Assigned(PopupMenu) then
     PopupMenu.Autopopup := False;
     PopupMenu.Autopopup := False;
 
 
-  OLECheck(SHGetDesktopFolder(FDesktop));
-
   FDragDropFilesEx.SourceEffects := [deCopy, deMove, deLink];
   FDragDropFilesEx.SourceEffects := [deCopy, deMove, deLink];
   FDragDropFilesEx.TargetEffects := [deCopy, deMove, deLink];
   FDragDropFilesEx.TargetEffects := [deCopy, deMove, deLink];
 
 
@@ -1264,71 +1247,6 @@ begin
   end;
   end;
 end; {GetDriveText}
 end; {GetDriveText}
 
 
-procedure TDriveView.GetNodeShellAttr(ParentNode: TTreeNode; NodeData: TNodeData; GetAttr: Boolean);
-var
-  ParentFolder: IShellFolder;
-  ParentData: TNodeData;
-begin
-  NodeData.shAttr := 0;
-
-  if GetAttr then
-  begin
-    if Assigned(ParentNode) then
-    begin
-      ParentData := TNodeData(ParentNode.Data);
-      if not Assigned(ParentData) then
-      begin
-        Assert(False);
-        ParentFolder := nil;
-      end
-        else
-      begin
-        if not Assigned(ParentData.ShellFolder) then
-        begin
-          GetNodeShellAttr(ParentNode.Parent, ParentData, GetAttr);
-        end;
-        ParentFolder := ParentData.ShellFolder;
-      end;
-    end
-      else
-    begin
-      ParentFolder := FDesktop;
-    end;
-
-    if Assigned(ParentFolder) and Assigned(NodeData) then
-    begin
-      if not Assigned(NodeData.PIDL) then
-        NodeData.PIDL := PIDL_GetFromParentFolder(ParentFolder, PChar(NodeData.DirName));
-      if Assigned(NodeData.PIDL) then
-      begin
-        NodeData.shAttr := SFGAO_CONTENTSMASK;
-
-        // Previously we would also make use of SFGAO_SHARE to display a share overlay.
-        // But for directories, Windows File Explorer does not display the overlay anymore (probably since Vista).
-        // And for drives (where Explorer does display the overlay), it did not work ever since we use "desktop"
-        // (and not "workspace" as before) to resolve drive interface (see Bug 1717).
-        if not Succeeded(ShellFolderGetAttributesOfWithTimeout(ParentFolder, 1, NodeData.PIDL, NodeData.shAttr, MSecsPerSec)) then
-        begin
-          NodeData.shAttr := 0;
-        end;
-
-        if not Assigned(NodeData.ShellFolder) then
-        begin
-          ParentFolder.BindToObject(NodeData.PIDL, nil, IID_IShellFolder, Pointer(NodeData.ShellFolder));
-        end;
-      end
-    end;
-  end;
-
-  if NodeData.shAttr = 0 then
-  begin
-    // If we cannot resolve attrs, we do not want to assume that the folder has no subfolders,
-    // as that will make us scan the folder.
-    NodeData.shAttr := SFGAO_HASSUBFOLDER;
-  end;
-
-end; {GetNodeAttr}
-
 function CompareDrive(List: TStringList; Index1, Index2: Integer): Integer;
 function CompareDrive(List: TStringList; Index1, Index2: Integer): Integer;
 var
 var
   Drive1, Drive2: string;
   Drive1, Drive2: string;
@@ -1414,7 +1332,6 @@ var
   NextDriveNode: TTreeNode;
   NextDriveNode: TTreeNode;
   Index: Integer;
   Index: Integer;
   Drive: string;
   Drive: string;
-  GetAttr: Boolean;
 begin
 begin
   SaveCursor := Screen.Cursor;
   SaveCursor := Screen.Cursor;
   Screen.Cursor := crHourGlass;
   Screen.Cursor := crHourGlass;
@@ -1449,12 +1366,6 @@ begin
               NodeData := TNodeData.Create;
               NodeData := TNodeData.Create;
               NodeData.DirName := DriveInfo.GetDriveRoot(Drive);
               NodeData.DirName := DriveInfo.GetDriveRoot(Drive);
 
 
-              {Get the shared attributes:}
-              GetAttr :=
-                DriveInfo.IsFixedDrive(Drive) and (DriveType <> DRIVE_REMOVABLE) and
-                ((DriveType <> DRIVE_REMOTE) or GetNetWorkConnected(Drive));
-              GetNodeShellAttr(nil, NodeData, GetAttr);
-
               if Assigned(NextDriveNode) then
               if Assigned(NextDriveNode) then
                 RootNode := Items.InsertObject(NextDriveNode, '', NodeData)
                 RootNode := Items.InsertObject(NextDriveNode, '', NodeData)
               else
               else
@@ -1504,11 +1415,11 @@ begin
   end;
   end;
 end; {RefreshRootNodes}
 end; {RefreshRootNodes}
 
 
-function TDriveView.AddChildNode(ParentNode: TTreeNode; SRec: TSearchRec): TTreeNode;
+function TDriveView.AddChildNode(ParentNode: TTreeNode; ParentPath: string; SRec: TSearchRec): TTreeNode;
 var
 var
   NewNode: TTreeNode;
   NewNode: TTreeNode;
   NodeData: TNodeData;
   NodeData: TNodeData;
-  GetAttr: Boolean;
+  SubSRec: TSearchRec;
 begin
 begin
   NodeData := TNodeData.Create;
   NodeData := TNodeData.Create;
   NodeData.Attr := SRec.Attr;
   NodeData.Attr := SRec.Attr;
@@ -1520,20 +1431,28 @@ begin
      SameText(SRec.Name, 'RECYCLER') or
      SameText(SRec.Name, 'RECYCLER') or
      SameText(SRec.Name, '$RECYCLE.BIN'));
      SameText(SRec.Name, '$RECYCLE.BIN'));
 
 
-  { query content attributes ("has subfolder") only if tree view is visible }
-  { to avoid unnecessary scan of subfolders (which may take some time) }
-  { if tree view is not visible anyway }
-  GetAttr :=
-    Visible and
-    Assigned(Parent) and
-    // Ad-hoc test for "other/right" panel, which is not hidden directly, but indirectly by hiding it container panel
-    ((Parent is TCustomForm) or Parent.Visible) and
-    (GetDriveTypeToNode(ParentNode) <> DRIVE_REMOTE);
-  GetNodeShellAttr(ParentNode, NodeData, GetAttr);
-
   NewNode := Self.Items.AddChildObject(ParentNode, '', NodeData);
   NewNode := Self.Items.AddChildObject(ParentNode, '', NodeData);
   NewNode.Text := GetDisplayName(NewNode);
   NewNode.Text := GetDisplayName(NewNode);
 
 
+  // query content ("has subfolder") only if tree view is visible
+  // to avoid unnecessary scan of subfolders (which may take some time)
+  // if tree view is not visible anyway
+  if Visible and
+     Assigned(Parent) and
+     // Ad-hoc test for "other/right" panel, which is not hidden directly, but indirectly by hiding it container panel
+     ((Parent is TCustomForm) or Parent.Visible) and
+     (GetDriveTypeToNode(ParentNode) <> DRIVE_REMOTE) then
+  begin
+    NewNode.HasChildren := FindFirstSubDir(IncludeTrailingBackslash(ParentPath) + NodeData.DirName + '\*.*', SubSRec);
+    FindClose(SubSRec);
+  end
+    else
+  begin
+    NewNode.HasChildren := True;
+  end;
+
+  NodeData.Scanned := not NewNode.HasChildren;
+
   Result := NewNode;
   Result := NewNode;
 end; {AddChildNode}
 end; {AddChildNode}
 
 
@@ -1730,28 +1649,6 @@ begin
   Result := DoFindNodeToPath(Path, True);
   Result := DoFindNodeToPath(Path, True);
 end;
 end;
 
 
-function TDriveView.CheckForSubDirs(Path: string): Boolean;
-var
-  DosError: Integer;
-  SRec: TSearchRec;
-begin
-  Result := False;
-
-  DosError := FindFirst(ApiPath(IncludeTrailingBackslash(Path) + '*.'), DirAttrMask, SRec);
-  while DosError = 0 do
-  begin
-    if (SRec.Name <> '.' ) and
-       (SRec.Name <> '..') and
-       (SRec.Attr and faDirectory <> 0) then
-    begin
-      Result := True;
-      Break;
-    end;
-    DosError := FindNext(SRec);
-  end;
-  FindClose(SRec);
-end; {CheckForSubDirs}
-
 function TDriveView.GetSubDir(var SRec: TSearchRec): Boolean;
 function TDriveView.GetSubDir(var SRec: TSearchRec): Boolean;
 begin
 begin
   Result := True;
   Result := True;
@@ -1769,9 +1666,13 @@ end;
 
 
 function TDriveView.FindFirstSubDir(Path: string; var SRec: TSearchRec): Boolean;
 function TDriveView.FindFirstSubDir(Path: string; var SRec: TSearchRec): Boolean;
 begin
 begin
-  Result :=
-    (FindFirstEx(ApiPath(Path), DirAttrMask, SRec, FIND_FIRST_EX_LARGE_FETCH_PAS, FindExSearchLimitToDirectories) = 0) and
-    GetSubDir(SRec);
+  Result := (FindFirstEx(ApiPath(Path), DirAttrMask, SRec, FIND_FIRST_EX_LARGE_FETCH_PAS, FindExSearchLimitToDirectories) = 0);
+  if Result then
+  begin
+    Result := GetSubDir(SRec);
+    // For consistency with FindFirst, but not really needed, as all callers call FindClose unconditionally anyway
+    if not Result then FindClose(SRec);
+  end;
 end;
 end;
 
 
 function TDriveView.FindNextSubDir(var SRec: TSearchRec): Boolean;
 function TDriveView.FindNextSubDir(var SRec: TSearchRec): Boolean;
@@ -1783,11 +1684,9 @@ function TDriveView.ReadSubDirs(Node: TTreeNode; DriveType: Integer; SpecificFil
 var
 var
   C: Integer;
   C: Integer;
   SRec: TSearchRec;
   SRec: TSearchRec;
-  NewNode: TTreeNode;
   Path: string;
   Path: string;
   Start: TDateTime;
   Start: TDateTime;
   R, All, Stop: Boolean;
   R, All, Stop: Boolean;
-  Seconds: Integer;
 begin
 begin
   Result := False;
   Result := False;
   Path := NodePath(Node);
   Path := NodePath(Node);
@@ -1801,20 +1700,8 @@ begin
   // because EndUpdate triggers TVN_GETDISPINFO for all nodes in the tree.
   // because EndUpdate triggers TVN_GETDISPINFO for all nodes in the tree.
   while R do
   while R do
   begin
   begin
-    NewNode := AddChildNode(Node, SRec);
+    AddChildNode(Node, Path, SRec);
     Inc(C);
     Inc(C);
-    if DoScanDir(NewNode) then
-    begin
-      // We have seen the SFGAO_HASSUBFOLDER to be absent on C: drive $Recycle.Bin
-      NewNode.HasChildren := Bool(TNodeData(NewNode.Data).shAttr and SFGAO_HASSUBFOLDER);
-
-      TNodeData(NewNode.Data).Scanned := not NewNode.HasChildren;
-    end
-      else
-    begin
-      NewNode.HasChildren := False;
-      TNodeData(NewNode.Data).Scanned := True;
-    end;
 
 
     // There are two other directory reading loops, where this is not called
     // There are two other directory reading loops, where this is not called
     if ((C mod 100) = 0) and Assigned(OnContinueLoading) then
     if ((C mod 100) = 0) and Assigned(OnContinueLoading) then
@@ -1866,7 +1753,6 @@ function TDriveView.CallBackValidateDir(var Node: TTreeNode; Data: Pointer): Boo
 var
 var
   WorkNode: TTreeNode;
   WorkNode: TTreeNode;
   DelNode: TTreeNode;
   DelNode: TTreeNode;
-  NewNode: TTreeNode;
   SRec: TSearchRec;
   SRec: TSearchRec;
   SrecList: TStringList;
   SrecList: TStringList;
   SubDirList: TStringList;
   SubDirList: TStringList;
@@ -1923,10 +1809,7 @@ begin {CallBackValidateDir}
         if not SubDirList.Find(Srec.Name, Index) then
         if not SubDirList.Find(Srec.Name, Index) then
         {Subnode does not exists: add it:}
         {Subnode does not exists: add it:}
         begin
         begin
-          NewNode := AddChildNode(Node, SRec);
-          // Why doesn't this use shAttr consistently with other code?
-          NewNode.HasChildren := CheckForSubDirs(ParentDir + Srec.Name);
-          TNodeData(NewNode.Data).Scanned := Not NewNode.HasChildren;
+          AddChildNode(Node, ParentDir, SRec);
           NewDirFound  := True;
           NewDirFound  := True;
         end;
         end;
         R := FindNextSubDir(Srec);
         R := FindNextSubDir(Srec);

+ 2 - 48
source/packages/my/OperationWithTimeout.pas

@@ -5,9 +5,6 @@ interface
 uses
 uses
   Winapi.Windows, Winapi.ShlObj, Winapi.ShellAPI, ActiveX;
   Winapi.Windows, Winapi.ShlObj, Winapi.ShellAPI, ActiveX;
 
 
-function ShellFolderGetAttributesOfWithTimeout(
-  ShellFolder: IShellFolder; cidl: UINT; apidl: PItemIDList; var rgfInOut: UINT; Timeout: Integer): HResult;
-
 function SHGetFileInfoWithTimeout(
 function SHGetFileInfoWithTimeout(
   pszPath: LPCWSTR; dwFileAttributes: DWORD; var psfi: TSHFileInfoW;
   pszPath: LPCWSTR; dwFileAttributes: DWORD; var psfi: TSHFileInfoW;
   cbFileInfo, uFlags: UINT; Timeout: Integer): DWORD_PTR;
   cbFileInfo, uFlags: UINT; Timeout: Integer): DWORD_PTR;
@@ -34,14 +31,6 @@ type
 
 
   TOperation = class(TObject)
   TOperation = class(TObject)
   public
   public
-    // ShellFolderGetAttributesOfWithTimeout
-    ShellFolder: IShellFolder;
-    cidl: UINT;
-    apidl: PItemIDList;
-    rgfInOut: UINT;
-    Timeout: Integer;
-    ResultHResult: HResult;
-
     // SHGetFileInfoWithTimeout
     // SHGetFileInfoWithTimeout
     PIDL: PItemIDList;
     PIDL: PItemIDList;
     Path: string;
     Path: string;
@@ -51,7 +40,8 @@ type
     ResultDWordPtr: DWORD_PTR;
     ResultDWordPtr: DWORD_PTR;
 
 
     // ShellFolderParseDisplayNameWithTimeout
     // ShellFolderParseDisplayNameWithTimeout
-    // Uses ShellFolder and ResultHResult
+    ShellFolder: IShellFolder;
+    ResultHResult: HResult;
     hwndOwner: HWND;
     hwndOwner: HWND;
     pbcReserved: Pointer;
     pbcReserved: Pointer;
     DisplayName: string;
     DisplayName: string;
@@ -135,42 +125,6 @@ begin
 {$ENDIF}
 {$ENDIF}
 end;
 end;
 
 
-procedure ShellFolderGetAttributesOfOperation(Operation: TOperation);
-var
-  ErrorMode: Word;
-begin
-  ErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS or SEM_NOOPENFILEERRORBOX);
-  try
-    Operation.ResultHResult := Operation.ShellFolder.GetAttributesOf(Operation.cidl, Operation.apidl, Operation.rgfInOut);
-  except
-    Operation.ResultHResult := E_FAIL;
-  end;
-  SetErrorMode(ErrorMode);
-end;
-
-function ShellFolderGetAttributesOfWithTimeout(
-  ShellFolder: IShellFolder; cidl: UINT; apidl: PItemIDList; var rgfInOut: UINT; Timeout: Integer): HResult;
-var
-  Operation: TOperation;
-begin
-  Operation := TOperation.Create;
-  Operation.ShellFolder := ShellFolder;
-  Operation.cidl := cidl;
-  Operation.apidl := apidl;
-  Operation.rgfInOut := rgfInOut;
-  if WaitForOperation(Operation, ShellFolderGetAttributesOfOperation, Timeout) then
-  begin
-    rgfInOut := Operation.rgfInOut;
-    Result := Operation.ResultHResult;
-    Operation.Free;
-  end
-    else
-  begin
-    rgfInOut := 0;
-    Result := E_FAIL;
-  end;
-end;
-
 procedure SHGetFileInfoOperation(Operation: TOperation);
 procedure SHGetFileInfoOperation(Operation: TOperation);
 var
 var
   pszPath: LPCWSTR;
   pszPath: LPCWSTR;