瀏覽代碼

Using large fetch and not reading short names when reading directories for file panel

No noticeable effect seen neither for local drives (where it is apparently expected), nor for local network drives

Source commit: 98b0b7181b7c3da4540fbb967fa46120ac546115
Martin Prikryl 1 年之前
父節點
當前提交
9a54ec5f77
共有 3 個文件被更改,包括 150 次插入73 次删除
  1. 18 7
      source/packages/filemng/DirView.pas
  2. 72 64
      source/packages/filemng/DriveView.pas
  3. 60 2
      source/packages/my/PasTools.pas

+ 18 - 7
source/packages/filemng/DirView.pas

@@ -896,11 +896,21 @@ begin
 end;
 end;
 
 
 procedure TDirView.SetPath(Value: string);
 procedure TDirView.SetPath(Value: string);
+var
+  LongPath: string;
+  Len: Integer;
 begin
 begin
   // do checks before passing directory to drive view, because
   // do checks before passing directory to drive view, because
   // it would truncate non-existing directory to first superior existing
   // it would truncate non-existing directory to first superior existing
   Value := ReplaceStr(Value, '/', '\');
   Value := ReplaceStr(Value, '/', '\');
 
 
+  // Convert to long path
+  Len := GetLongPathName(PChar(ApiPath(Value)), nil, 0);
+  SetLength(LongPath, Len);
+  Len := GetLongPathName(PChar(ApiPath(Value)), PChar(LongPath), Len);
+  if Len > 0 then
+    Value := Copy(LongPath, 1, Len);
+
   CheckCanOpenDirectory(Value);
   CheckCanOpenDirectory(Value);
 
 
   if Assigned(FDriveView) and
   if Assigned(FDriveView) and
@@ -1346,8 +1356,8 @@ begin
       begin
       begin
         FParentFolder := GetShellFolder(PathName);
         FParentFolder := GetShellFolder(PathName);
 
 
-        DosError := SysUtils.FindFirst(ApiPath(IncludeTrailingPathDelimiter(FPath) + '*.*'),
-          FileAttr, SRec);
+        DosError :=
+          FindFirstEx(ApiPath(IncludeTrailingPathDelimiter(FPath) + '*.*'), FileAttr, SRec, FIND_FIRST_EX_LARGE_FETCH_PAS);
         while (DosError = 0) and (not AbortLoading) do
         while (DosError = 0) and (not AbortLoading) do
         begin
         begin
           if (SRec.Attr and faDirectory) = 0 then
           if (SRec.Attr and faDirectory) = 0 then
@@ -1368,8 +1378,8 @@ begin
 
 
         {Search for directories:}
         {Search for directories:}
         DirsCount := 0;
         DirsCount := 0;
-        DosError := SysUtils.FindFirst(ApiPath(IncludeTrailingPathDelimiter(FPath) + '*.*'),
-          DirAttrMask, SRec);
+        DosError :=
+          FindFirstEx(ApiPath(IncludeTrailingPathDelimiter(FPath) + '*.*'), DirAttrMask, SRec, FIND_FIRST_EX_LARGE_FETCH_PAS);
         while (DosError = 0) and (not AbortLoading) do
         while (DosError = 0) and (not AbortLoading) do
         begin
         begin
           if (SRec.Name <> '.') and (SRec.Name <> '..') and
           if (SRec.Name <> '.') and (SRec.Name <> '..') and
@@ -1506,8 +1516,8 @@ begin
           end;
           end;
           EItems.Sort;
           EItems.Sort;
 
 
-          DosError := SysUtils.FindFirst(ApiPath(IncludeTrailingPathDelimiter(FPath) + '*.*'),
-            FileAttr, SRec);
+          DosError :=
+            FindFirstEx(ApiPath(IncludeTrailingPathDelimiter(FPath) + '*.*'), FileAttr, SRec, FIND_FIRST_EX_LARGE_FETCH_PAS);
           while DosError = 0 do
           while DosError = 0 do
           begin
           begin
             if (SRec.Attr and faDirectory) = 0 then
             if (SRec.Attr and faDirectory) = 0 then
@@ -1562,7 +1572,8 @@ begin
           SysUtils.FindClose(Srec);
           SysUtils.FindClose(Srec);
 
 
           {Search new directories:}
           {Search new directories:}
-          DosError := SysUtils.FindFirst(ApiPath(FPath + '\*.*'), DirAttrMask, SRec);
+          DosError :=
+            FindFirstEx(ApiPath(FPath + '\*.*'), DirAttrMask, SRec, FIND_FIRST_EX_LARGE_FETCH_PAS);
           while DosError = 0 do
           while DosError = 0 do
           begin
           begin
             if (Srec.Attr and faDirectory) <> 0 then
             if (Srec.Attr and faDirectory) <> 0 then

+ 72 - 64
source/packages/filemng/DriveView.pas

@@ -89,7 +89,6 @@ type
   TNodeData = class
   TNodeData = class
   private
   private
     FDirName: string;
     FDirName: string;
-    FShortName: string;
     FAttr: Integer;
     FAttr: Integer;
     FScanned: Boolean;
     FScanned: Boolean;
     FData: Pointer;
     FData: Pointer;
@@ -105,7 +104,6 @@ type
     destructor Destroy; override;
     destructor Destroy; override;
 
 
     property DirName: string read FDirName write FDirName;
     property DirName: string read FDirName write FDirName;
-    property ShortName: string read FShortName write FShortName;
     property Attr: Integer read FAttr write FAttr;
     property Attr: Integer read FAttr write FAttr;
     property Scanned: Boolean read FScanned write FScanned;
     property Scanned: Boolean read FScanned write FScanned;
     property Data: Pointer read FData write FData;
     property Data: Pointer read FData write FData;
@@ -164,6 +162,9 @@ type
     procedure SignalDirDelete(Sender: TObject; Files: TStringList);
     procedure SignalDirDelete(Sender: TObject; Files: TStringList);
 
 
     function CheckForSubDirs(Path: string): Boolean;
     function CheckForSubDirs(Path: string): Boolean;
+    function GetSubDir(var SRec: TSearchRec): Boolean;
+    function FindFirstSubDir(Path: string; var SRec: TSearchRec): Boolean;
+    function FindNextSubDir(var SRec: TSearchRec): Boolean;
     function ReadSubDirs(Node: TTreeNode; DriveType: Integer; SpecificFile: string = ''): Boolean;
     function ReadSubDirs(Node: TTreeNode; DriveType: Integer; SpecificFile: string = ''): Boolean;
 
 
     {Callback-functions used by iteratesubtree:}
     {Callback-functions used by iteratesubtree:}
@@ -426,7 +427,6 @@ begin
   FAttr := 0;
   FAttr := 0;
   FScanned := False;
   FScanned := False;
   FDirName := '';
   FDirName := '';
-  FShortName := '';
   FIsRecycleBin := False;
   FIsRecycleBin := False;
   FIconEmpty := True;
   FIconEmpty := True;
   shAttr := 0;
   shAttr := 0;
@@ -458,7 +458,6 @@ begin
     SourceData := TNodeData(TTreeNode(Source).Data);
     SourceData := TNodeData(TTreeNode(Source).Data);
     NewData := TNodeData.Create();
     NewData := TNodeData.Create();
     NewData.DirName := SourceData.DirName;
     NewData.DirName := SourceData.DirName;
-    NewData.ShortName := SourceData.ShortName;
     NewData.Attr := SourceData.Attr;
     NewData.Attr := SourceData.Attr;
     NewData.Scanned := SourceData.Scanned;
     NewData.Scanned := SourceData.Scanned;
     NewData.Data := SourceData.Data;
     NewData.Data := SourceData.Data;
@@ -885,7 +884,6 @@ end; {CanEdit}
 
 
 procedure TDriveView.Edit(const Item: TTVItem);
 procedure TDriveView.Edit(const Item: TTVItem);
 var
 var
-  SRec: TSearchRec;
   Node: TTreeNode;
   Node: TTreeNode;
   Info: string;
   Info: string;
   i: Integer;
   i: Integer;
@@ -923,12 +921,6 @@ begin
       begin
       begin
         Node.Text := Item.pszText;
         Node.Text := Item.pszText;
         TNodeData(Node.Data).DirName := Item.pszText;
         TNodeData(Node.Data).DirName := Item.pszText;
-        if FindFirst(ApiPath(IncludeTrailingBackslash(NodePath(Node.Parent)) + Item.pszText),
-             faAnyFile, SRec) = 0 then
-        begin
-          TNodeData(Node.Data).ShortName := string(SRec.FindData.cAlternateFileName);
-        end;
-        FindClose(SRec);
         SortChildren(Node.Parent, False);
         SortChildren(Node.Parent, False);
 
 
         inherited;
         inherited;
@@ -1456,7 +1448,6 @@ begin
               { Create root directory node }
               { Create root directory node }
               NodeData := TNodeData.Create;
               NodeData := TNodeData.Create;
               NodeData.DirName := DriveInfo.GetDriveRoot(Drive);
               NodeData.DirName := DriveInfo.GetDriveRoot(Drive);
-              NodeData.ShortName := NodeData.DirName;
 
 
               {Get the shared attributes:}
               {Get the shared attributes:}
               GetAttr :=
               GetAttr :=
@@ -1522,7 +1513,6 @@ begin
   NodeData := TNodeData.Create;
   NodeData := TNodeData.Create;
   NodeData.Attr := SRec.Attr;
   NodeData.Attr := SRec.Attr;
   NodeData.DirName := SRec.Name;
   NodeData.DirName := SRec.Name;
-  NodeData.ShortName := SRec.FindData.cAlternateFileName;
   NodeData.FIsRecycleBin :=
   NodeData.FIsRecycleBin :=
     (SRec.Attr and faSysFile <> 0) and
     (SRec.Attr and faSysFile <> 0) and
     (ParentNode.Level = 0) and
     (ParentNode.Level = 0) and
@@ -1618,7 +1608,7 @@ function TDriveView.DoFindNodeToPath(Path: string; ExistingOnly: Boolean): TTree
     Result := nil;
     Result := nil;
     while Assigned(Node) do
     while Assigned(Node) do
     begin
     begin
-      if (UpperCase(GetDirName(Node)) = Dir) or (TNodeData(Node.Data).ShortName = Dir) then
+      if UpperCase(GetDirName(Node)) = Dir then
       begin
       begin
         if Length(Path) > 0 then
         if Length(Path) > 0 then
         begin
         begin
@@ -1762,62 +1752,85 @@ begin
   FindClose(SRec);
   FindClose(SRec);
 end; {CheckForSubDirs}
 end; {CheckForSubDirs}
 
 
+function TDriveView.GetSubDir(var SRec: TSearchRec): Boolean;
+begin
+  Result := True;
+  while Result and
+        ((SRec.Name = '.' ) or
+         (SRec.Name = '..') or
+         ((SRec.Attr and faDirectory) = 0)) do
+  begin
+    if FindNext(SRec) <> 0 then
+    begin
+      Result := False;
+    end;
+  end;
+end;
+
+function TDriveView.FindFirstSubDir(Path: string; var SRec: TSearchRec): Boolean;
+begin
+  Result :=
+    (FindFirstEx(ApiPath(Path), DirAttrMask, SRec, FIND_FIRST_EX_LARGE_FETCH_PAS, FindExSearchLimitToDirectories) = 0) and
+    GetSubDir(SRec);
+end;
+
+function TDriveView.FindNextSubDir(var SRec: TSearchRec): Boolean;
+begin
+  Result := (FindNext(SRec) = 0) and GetSubDir(SRec);
+end;
+
 function TDriveView.ReadSubDirs(Node: TTreeNode; DriveType: Integer; SpecificFile: string): Boolean;
 function TDriveView.ReadSubDirs(Node: TTreeNode; DriveType: Integer; SpecificFile: string): Boolean;
 var
 var
-  C, DosError: Integer;
+  C: Integer;
   SRec: TSearchRec;
   SRec: TSearchRec;
   NewNode: TTreeNode;
   NewNode: TTreeNode;
   Path: string;
   Path: string;
   Start: TDateTime;
   Start: TDateTime;
-  All, Stop: Boolean;
+  R, All, Stop: Boolean;
+  Seconds: Integer;
 begin
 begin
   Result := False;
   Result := False;
   Path := NodePath(Node);
   Path := NodePath(Node);
   All := (SpecificFile = '');
   All := (SpecificFile = '');
   if All then SpecificFile := '*.*';
   if All then SpecificFile := '*.*';
-  DosError := FindFirst(ApiPath(IncludeTrailingBackslash(Path) + SpecificFile), DirAttrMask, SRec);
+  R := FindFirstSubDir(IncludeTrailingBackslash(Path) + SpecificFile, SRec);
   Start := Now;
   Start := Now;
   C := 0;
   C := 0;
   // At least from SetDirectory > DoFindNodeToPath and CanExpand, this is not called within BeginUpdate/EndUpdate block.
   // At least from SetDirectory > DoFindNodeToPath and CanExpand, this is not called within BeginUpdate/EndUpdate block.
-  // But in any case, addinf it here makes expanding (which calls CanExpand) noticeably slower, when there are lot of nodes,
+  // But in any case, adding it here makes expanding (which calls CanExpand) noticeably slower, when there are lot of nodes,
   // because EndUpdate triggers TVN_GETDISPINFO for all nodes in the tree.
   // because EndUpdate triggers TVN_GETDISPINFO for all nodes in the tree.
-  while DosError = 0 do
+  while R do
   begin
   begin
-    if (SRec.Name <> '.' ) and
-       (SRec.Name <> '..') and
-       (SRec.Attr and faDirectory <> 0) then
+    NewNode := AddChildNode(Node, SRec);
+    Inc(C);
+    if DoScanDir(NewNode) then
     begin
     begin
-      NewNode := AddChildNode(Node, SRec);
-      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);
+      // 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
-      if ((C mod 100) = 0) and Assigned(OnContinueLoading) then
-      begin
-        Stop := False;
-        OnContinueLoading(Self, Start, Path, C, Stop);
-        if Stop then DosError := 1;
-      end;
+      TNodeData(NewNode.Data).Scanned := not NewNode.HasChildren;
+    end
+      else
+    begin
+      NewNode.HasChildren := False;
+      TNodeData(NewNode.Data).Scanned := True;
+    end;
 
 
-      Result := True;
+    // There are two other directory reading loops, where this is not called
+    if ((C mod 100) = 0) and Assigned(OnContinueLoading) then
+    begin
+      Stop := False;
+      OnContinueLoading(Self, Start, Path, C, Stop);
+      if Stop then R := False;
     end;
     end;
 
 
-    if DosError = 0 then
+    Result := True;
+
+    if R then
     begin
     begin
-      DosError := FindNext(SRec);
+      R := FindNextSubDir(SRec);
     end;
     end;
-  end; {While DosError = 0}
+  end;
 
 
   FindClose(Srec);
   FindClose(Srec);
 
 
@@ -1857,7 +1870,7 @@ var
   SRec: TSearchRec;
   SRec: TSearchRec;
   SrecList: TStringList;
   SrecList: TStringList;
   SubDirList: TStringList;
   SubDirList: TStringList;
-  DosError: Integer;
+  R: Boolean;
   Index: Integer;
   Index: Integer;
   NewDirFound: Boolean;
   NewDirFound: Boolean;
   ParentDir: string;
   ParentDir: string;
@@ -1903,24 +1916,20 @@ begin {CallBackValidateDir}
 
 
       SRecList := TStringList.Create;
       SRecList := TStringList.Create;
       SRecList.CaseSensitive := True;
       SRecList.CaseSensitive := True;
-      DosError := FindFirst(ApiPath(ParentDir + '*.*'), DirAttrMask, SRec);
-      while DosError = 0 do
+      R := FindFirstSubDir(ParentDir + '*.*', SRec);
+      while R do
       begin
       begin
-        if (Srec.Name <> '.' ) and
-           (Srec.Name <> '..') and
-           (Srec.Attr and faDirectory <> 0) then
+        SrecList.Add(Srec.Name);
+        if not SubDirList.Find(Srec.Name, Index) then
+        {Subnode does not exists: add it:}
         begin
         begin
-          SrecList.Add(Srec.Name);
-          if not SubDirList.Find(Srec.Name, Index) then
-          {Subnode does not exists: add it:}
-          begin
-            NewNode := AddChildNode(Node, SRec);
-            NewNode.HasChildren := CheckForSubDirs(ParentDir + Srec.Name);
-            TNodeData(NewNode.Data).Scanned := Not NewNode.HasChildren;
-            NewDirFound  := True;
-          end;
+          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;
+          NewDirFound  := True;
         end;
         end;
-        DosError := FindNext(Srec);
+        R := FindNextSubDir(Srec);
       end;
       end;
       FindClose(Srec);
       FindClose(Srec);
       Sreclist.Sort;
       Sreclist.Sort;
@@ -1942,7 +1951,6 @@ begin {CallBackValidateDir}
           begin
           begin
             {Case of directory letters has changed:}
             {Case of directory letters has changed:}
             TNodeData(WorkNode.Data).DirName := SrecList[Index];
             TNodeData(WorkNode.Data).DirName := SrecList[Index];
-            TNodeData(WorkNode.Data).ShortName := ExtractShortPathName(NodePathName(WorkNode));
             WorkNode.Text := SrecList[Index];
             WorkNode.Text := SrecList[Index];
           end;
           end;
           WorkNode := Node.GetNextChild(WorkNode);
           WorkNode := Node.GetNextChild(WorkNode);

+ 60 - 2
source/packages/my/PasTools.pas

@@ -3,7 +3,7 @@ unit PasTools;
 interface
 interface
 
 
 uses
 uses
-  Windows, Types, Classes, ComCtrls, ExtCtrls, Controls, Dialogs, Forms, Messages, Graphics;
+  Windows, Types, Classes, ComCtrls, ExtCtrls, Controls, Dialogs, Forms, Messages, Graphics, SysUtils;
 
 
 function Construct(ComponentClass: TComponentClass; Owner: TComponent): TComponent;
 function Construct(ComponentClass: TComponentClass; Owner: TComponent): TComponent;
 
 
@@ -77,6 +77,12 @@ function IsUncPath(Path: string): Boolean;
 function FileExistsFix(Path: string): Boolean;
 function FileExistsFix(Path: string): Boolean;
 function DirectoryExistsFix(Path: string): Boolean;
 function DirectoryExistsFix(Path: string): Boolean;
 
 
+const
+  FIND_FIRST_EX_LARGE_FETCH_PAS = 2; // VCLCOPY (actually should be part of Winapi)
+function FindFirstEx(
+  const Path: string; Attr: Integer; var F: TSearchRec; AdditionalFlags: DWORD = 0;
+  SearchOp: _FINDEX_SEARCH_OPS = FindExSearchNameMatch): Integer;
+
 function SupportsDarkMode: Boolean;
 function SupportsDarkMode: Boolean;
 procedure AllowDarkModeForWindow(Control: TWinControl; Allow: Boolean); overload;
 procedure AllowDarkModeForWindow(Control: TWinControl; Allow: Boolean); overload;
 procedure AllowDarkModeForWindow(Handle: THandle; Allow: Boolean); overload;
 procedure AllowDarkModeForWindow(Handle: THandle; Allow: Boolean); overload;
@@ -153,7 +159,7 @@ type
 implementation
 implementation
 
 
 uses
 uses
-  SysUtils, StdCtrls, MultiMon, ShellAPI, Generics.Collections, CommCtrl, ImgList, Registry;
+  StdCtrls, MultiMon, ShellAPI, Generics.Collections, CommCtrl, ImgList, Registry;
 
 
 const
 const
   DDExpandDelay = 15000000;
   DDExpandDelay = 15000000;
@@ -1045,6 +1051,57 @@ begin
   Result := DoExists(DirectoryExists(ApiPath(Path)), Path);
   Result := DoExists(DirectoryExists(ApiPath(Path)), Path);
 end;
 end;
 
 
+// VCLCOPY
+function FindMatchingFileEx(var F: TSearchRec): Integer;
+var
+  LocalFileTime: TFileTime;
+begin
+  while F.FindData.dwFileAttributes and F.ExcludeAttr <> 0 do
+    if not FindNextFile(F.FindHandle, F.FindData) then
+    begin
+      Result := GetLastError;
+      Exit;
+    end;
+  FileTimeToLocalFileTime(F.FindData.ftLastWriteTime, LocalFileTime);
+{$WARN SYMBOL_DEPRECATED OFF}
+  FileTimeToDosDateTime(LocalFileTime, LongRec(F.Time).Hi,
+    LongRec(F.Time).Lo);
+{$WARN SYMBOL_DEPRECATED ON}
+  F.Size := F.FindData.nFileSizeLow or Int64(F.FindData.nFileSizeHigh) shl 32;
+  F.Attr := F.FindData.dwFileAttributes;
+  F.Name := F.FindData.cFileName;
+  Result := 0;
+end;
+
+var
+  FindexAdvancedSupport: Boolean = False;
+
+// VCLCOPY (with FindFirstFile replaced by FindFirstFileEx)
+function FindFirstEx(
+  const Path: string; Attr: Integer; var F: TSearchRec; AdditionalFlags: DWORD; SearchOp: _FINDEX_SEARCH_OPS): Integer;
+const
+  faSpecial = faHidden or faSysFile or faDirectory;
+var
+  FindexInfoLevel: TFindexInfoLevels;
+begin
+  F.ExcludeAttr := not Attr and faSpecial;
+  // FindExInfoBasic = do not retrieve cAlternateFileName, which we do not use
+  if FindexAdvancedSupport then FindexInfoLevel := FindExInfoBasic
+    else
+  begin
+    FindexInfoLevel := FindExInfoStandard;
+    AdditionalFlags := AdditionalFlags and (not FIND_FIRST_EX_LARGE_FETCH_PAS);
+  end;
+  F.FindHandle := FindFirstFileEx(PChar(Path), FindexInfoLevel, @F.FindData, SearchOp, nil, AdditionalFlags);
+  if F.FindHandle <> INVALID_HANDLE_VALUE then
+  begin
+    Result := FindMatchingFileEx(F);
+    if Result <> 0 then FindClose(F);
+  end
+  else
+    Result := GetLastError;
+end;
+
 type TPreferredAppMode = (pamDefault, pamAllowDark, pamForceDark, pamForceLight, pamMax);
 type TPreferredAppMode = (pamDefault, pamAllowDark, pamForceDark, pamForceLight, pamMax);
 
 
 var
 var
@@ -1139,6 +1196,7 @@ var
   OSVersionInfo: TOSVersionInfoEx;
   OSVersionInfo: TOSVersionInfoEx;
   SetDefaultDllDirectories: function(DirectoryFlags: DWORD): BOOL; stdcall;
   SetDefaultDllDirectories: function(DirectoryFlags: DWORD): BOOL; stdcall;
 initialization
 initialization
+  FindexAdvancedSupport := IsWin7;
   // Translated from PuTTY's dll_hijacking_protection().
   // Translated from PuTTY's dll_hijacking_protection().
   // Inno Setup does not use LOAD_LIBRARY_SEARCH_USER_DIRS and falls back to SetDllDirectory.
   // Inno Setup does not use LOAD_LIBRARY_SEARCH_USER_DIRS and falls back to SetDllDirectory.
   Lib := LoadLibrary(kernel32);
   Lib := LoadLibrary(kernel32);