Skip to content

Commit

Permalink
Issue 2264 – Allow aborting local directory tree loading when it is t…
Browse files Browse the repository at this point in the history
…aking too long

https://winscp.net/tracker/2264

Source commit: f08fad8a3c94361f9061d19b30b22a7f88f5e77a
  • Loading branch information
martinprikryl committed Feb 23, 2024
1 parent bcf29af commit e23d054
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 35 deletions.
26 changes: 26 additions & 0 deletions source/forms/ScpCommander.cpp
Expand Up @@ -15,6 +15,7 @@
#include <DragDrop.hpp>
#include <StrUtils.hpp>
#include <IOUtils.hpp>
#include <DateUtils.hpp>

#include "Glyphs.h"
#include "NonVisual.h"
Expand Down Expand Up @@ -3010,3 +3011,28 @@ void TScpCommanderForm::RestoreFocus(void * Focus)
ActiveControl = ControlFocus;
}
}
//---------------------------------------------------------------------------
void __fastcall TScpCommanderForm::LocalDriveViewContinueLoading(
TObject *, TDateTime & Start, UnicodeString Path, int Count, bool & Stop)
{
int Limit = WinConfiguration->LoadingTooLongLimit;
if ((Limit > 0) &&
(SecondsBetween(Now(), Start) > Limit))
{
UnicodeString Message = FMTLOAD(CONTINUE_DIR_LOADING, (Path, FormatNumber(Count)));
TMessageParams Params(mpNeverAskAgainCheck);
unsigned int Answer = MessageDialog(Message, qtConfirmation, qaOK | qaCancel, HELP_NONE, &Params);
if (Answer == qaCancel)
{
Stop = true;
}
else
{
if (Answer == qaNeverAskAgain)
{
WinConfiguration->LoadingTooLongLimit = 0;
}
Start = Now();
}
}
}
2 changes: 2 additions & 0 deletions source/forms/ScpCommander.dfm
Expand Up @@ -1239,6 +1239,7 @@ inherited ScpCommanderForm: TScpCommanderForm
WatchDirectory = True
DirView = OtherLocalDirView
OnRefreshDrives = LocalDriveViewRefreshDrives
OnContinueLoading = LocalDriveViewContinueLoading
OnBusy = DirViewBusy
OnDDDragEnter = LocalFileControlDDDragEnter
OnDDDragLeave = FileControlDDDragLeave
Expand Down Expand Up @@ -1832,6 +1833,7 @@ inherited ScpCommanderForm: TScpCommanderForm
WatchDirectory = True
DirView = LocalDirView
OnRefreshDrives = LocalDriveViewRefreshDrives
OnContinueLoading = LocalDriveViewContinueLoading
OnBusy = DirViewBusy
OnDDDragEnter = LocalFileControlDDDragEnter
OnDDDragLeave = FileControlDDDragLeave
Expand Down
1 change: 1 addition & 0 deletions source/forms/ScpCommander.h
Expand Up @@ -531,6 +531,7 @@ class TScpCommanderForm : public TCustomScpExplorerForm
void __fastcall OtherLocalDirViewUpdateStatusBar(TObject *Sender, const TStatusFileInfo &FileInfo);
void __fastcall OtherLocalDirViewPathChange(TCustomDirView *Sender);
void __fastcall LocalDriveViewNeedHiddenDirectories(TObject *Sender);
void __fastcall LocalDriveViewContinueLoading(TObject *Sender, TDateTime &Start, UnicodeString Path, int Count, bool &Stop);

private:
bool FConstructed;
Expand Down
119 changes: 84 additions & 35 deletions source/packages/filemng/DriveView.pas
Expand Up @@ -118,6 +118,7 @@ TDriveTreeNode = class(TTreeNode)
end;

TDriveViewRefreshDrives = procedure(Sender: TObject; Global: Boolean) of object;
TDriveViewContinueLoading = procedure(Sender: TObject; var Start: TDateTime; Path: string; Count: Integer; var Stop: Boolean) of object;

TDriveView = class(TCustomDriveView)
private
Expand Down Expand Up @@ -149,6 +150,7 @@ TDriveView = class(TCustomDriveView)
FOnDisplayContextMenu: TNotifyEvent;
FOnRefreshDrives: TDriveViewRefreshDrives;
FOnNeedHiddenDirectories: TNotifyEvent;
FOnContinueLoading: TDriveViewContinueLoading;

{used components:}
FDirView: TDirView;
Expand All @@ -163,7 +165,7 @@ TDriveView = class(TCustomDriveView)
procedure SignalDirDelete(Sender: TObject; Files: TStringList);

function CheckForSubDirs(Path: string): Boolean;
function ReadSubDirs(Node: TTreeNode; DriveType: Integer): Boolean;
function ReadSubDirs(Node: TTreeNode; DriveType: Integer; SpecificFile: string = ''): Boolean;

{Callback-functions used by iteratesubtree:}
function CallBackValidateDir(var Node: TTreeNode; Data: Pointer): Boolean;
Expand Down Expand Up @@ -318,6 +320,7 @@ TDriveView = class(TCustomDriveView)
property OnDisplayContextMenu: TNotifyEvent read FOnDisplayContextMenu
write FOnDisplayContextMenu;
property OnRefreshDrives: TDriveViewRefreshDrives read FOnRefreshDrives write FOnRefreshDrives;
property OnContinueLoading: TDriveViewContinueLoading read FOnContinueLoading write FOnContinueLoading;
property OnBusy;

property DDLinkOnExeDrag;
Expand Down Expand Up @@ -1653,20 +1656,26 @@ function TDriveView.DoFindNodeToPath(Path: string; ExistingOnly: Boolean): TTree

function SearchSubDirs(ParentNode: TTreeNode; Path: string): TTreeNode; forward;

function ExtractFirstName(S: string): string;
var
I: Integer;
begin
I := Pos('\', S);

if I = 0 then
I := Length(S);

Result := System.Copy(S, 1, I);
end;

function DoSearchSubDirs(ParentNode: TTreeNode; Path: string): TTreeNode;
var
i: Integer;
Node: TTreeNode;
Dir: string;
begin
{Extract first directory from path:}
i := Pos('\', Path);

if i = 0 then
i := Length(Path);

Dir := System.Copy(Path, 1, i);
System.Delete(Path, 1, i);
Dir := ExtractFirstName(Path);
System.Delete(Path, 1, Length(Dir));

if Dir[Length(Dir)] = '\' then
SetLength(Dir, Pred(Length(Dir)));
Expand Down Expand Up @@ -1704,11 +1713,19 @@ function TDriveView.DoFindNodeToPath(Path: string; ExistingOnly: Boolean): TTree
begin
if (not TNodeData(ParentNode.Data).Scanned) and (not ExistingOnly) then
begin
ReadSubDirs(ParentNode, GetDriveTypetoNode(ParentNode));
ReadSubDirs(ParentNode, GetDriveTypeToNode(ParentNode));
end;

// Factored out of DoSearchSubDirs is remnant of Bug 956 superceded by Bug 1320
Result := DoSearchSubDirs(ParentNode, Path);

if (not Assigned(Result)) and
DirectoryExists(IncludeTrailingBackslash(NodePath(ParentNode)) + Path) and
(not ExistingOnly) then
begin
ReadSubDirs(ParentNode, GetDriveTypeToNode(ParentNode), ExcludeTrailingBackslash(ExtractFirstName(Path)));
Result := DoSearchSubDirs(ParentNode, Path);
end;
end;
end; {SearchSubDirs}

Expand Down Expand Up @@ -1817,42 +1834,74 @@ function TDriveView.CheckForSubDirs(Path: string): Boolean;
FindClose(SRec);
end; {CheckForSubDirs}

function TDriveView.ReadSubDirs(Node: TTreeNode; DriveType: Integer): Boolean;
function TDriveView.ReadSubDirs(Node: TTreeNode; DriveType: Integer; SpecificFile: string): Boolean;
var
DosError: Integer;
C, DosError: Integer;
SRec: TSearchRec;
NewNode: TTreeNode;
Path: string;
Start: TDateTime;
All, Stop: Boolean;
begin
Result := False;
DosError := FindFirst(ApiPath(IncludeTrailingBackslash(NodePath(Node)) + '*.*'), DirAttrMask, SRec);
while DosError = 0 do
begin
if (SRec.Name <> '.' ) and
(SRec.Name <> '..') and
(SRec.Attr and faDirectory <> 0) then
Path := NodePath(Node);
All := (SpecificFile = '');
if All then SpecificFile := '*.*';
DosError := FindFirst(ApiPath(IncludeTrailingBackslash(Path) + SpecificFile), DirAttrMask, SRec);
Start := Now;
C := 0;
// At least from SetDirectory > DoFindNodeToPath, this is not called within BeginUpdate/EndUpdate block.
// No noticeable effect seen (possibly because the node is collapsed), but it makes sense in general.
Items.BeginUpdate;
try
while DosError = 0 do
begin
NewNode := AddChildNode(Node, SRec);
if DoScanDir(NewNode) then
if (SRec.Name <> '.' ) and
(SRec.Name <> '..') and
(SRec.Attr and faDirectory <> 0) 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);
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);

TNodeData(NewNode.Data).Scanned := not NewNode.HasChildren;
end
else
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;

Result := True;
end;

if DosError = 0 then
begin
NewNode.HasChildren := False;
TNodeData(NewNode.Data).Scanned := True;
DosError := FindNext(SRec);
end;
Result := True;
end;
DosError := FindNext(SRec);
end; {While DosError = 0}
FindClose(Srec);
TNodeData(Node.Data).Scanned := True;
end; {While DosError = 0}

if Result then SortChildren(Node, False)
else Node.HasChildren := False;
FindClose(Srec);

if All then TNodeData(Node.Data).Scanned := True;

if Result then SortChildren(Node, False)
else
if All then Node.HasChildren := False;
finally
Items.EndUpdate;
end;
Application.ProcessMessages;
end; {ReadSubDirs}

Expand Down
1 change: 1 addition & 0 deletions source/resource/TextsWin.h
Expand Up @@ -161,6 +161,7 @@
#define CLOSE_WORKSPACE 1372
#define TOO_MANY_SESSIONS 1373
#define EDIT_CHANGED_EXTERNALLY 1374
#define CONTINUE_DIR_LOADING 1375

#define WIN_INFORMATION_STRINGS 1400
#define COMPARE_NO_DIFFERENCES 1402
Expand Down
1 change: 1 addition & 0 deletions source/resource/TextsWin1.rc
Expand Up @@ -165,6 +165,7 @@ BEGIN
CLOSE_WORKSPACE, "Close application without saving a workspace?"
TOO_MANY_SESSIONS, "**Do you really want to open another tab?**\nYou have %d tabs opened already. Please consider closing some tabs first to free resources of your computer."
EDIT_CHANGED_EXTERNALLY, "The remote file has been changed while you were editing it. Do you want to overwrite it anyway?"
CONTINUE_DIR_LOADING, "**Directory '%s' is taking too long to load. Do you want to keep waiting?**\nAlternatively, select 'Cancel' to abort loading and continue with incomplete results. %s entries have been loaded so far.\n\nIf you are getting this on every WinSCP start, and you are not using this directory, consider browsing to a different path before closing WinSCP, not to start in this directory the next time."

WIN_INFORMATION_STRINGS, "WIN_INFORMATION"
COMPARE_NO_DIFFERENCES, "No differences found."
Expand Down
7 changes: 7 additions & 0 deletions source/windows/WinConfiguration.cpp
Expand Up @@ -636,6 +636,7 @@ void __fastcall TWinConfiguration::Default()
HiContrast = false;
EditorCheckNotModified = false;
SessionTabCaptionTruncation = true;
LoadingTooLongLimit = 60;
FirstRun = StandardDatestamp();

FEditor.Font.FontName = DefaultFixedWidthFontName;
Expand Down Expand Up @@ -1101,6 +1102,7 @@ THierarchicalStorage * TWinConfiguration::CreateScpStorage(bool & SessionList)
KEY(Bool, HiContrast); \
KEY(Bool, EditorCheckNotModified); \
KEY(Bool, SessionTabCaptionTruncation); \
KEY(Integer, LoadingTooLongLimit); \
KEY(String, FirstRun); \
); \
BLOCK(L"Interface\\Editor", CANCREATE, \
Expand Down Expand Up @@ -2813,6 +2815,11 @@ void TWinConfiguration::SetSessionTabCaptionTruncation(bool value)
SET_CONFIG_PROPERTY(SessionTabCaptionTruncation);
}
//---------------------------------------------------------------------------
void TWinConfiguration::SetLoadingTooLongLimit(int value)
{
SET_CONFIG_PROPERTY(LoadingTooLongLimit);
}
//---------------------------------------------------------------------------
void TWinConfiguration::SetFirstRun(const UnicodeString & value)
{
SET_CONFIG_PROPERTY(FirstRun);
Expand Down
3 changes: 3 additions & 0 deletions source/windows/WinConfiguration.h
Expand Up @@ -484,6 +484,7 @@ class TWinConfiguration : public TCustomWinConfiguration
bool FHiContrast;
bool FEditorCheckNotModified;
bool FSessionTabCaptionTruncation;
int FLoadingTooLongLimit;
UnicodeString FFirstRun;
int FDontDecryptPasswords;
int FMasterPasswordSession;
Expand Down Expand Up @@ -607,6 +608,7 @@ class TWinConfiguration : public TCustomWinConfiguration
void SetHiContrast(bool value);
void SetEditorCheckNotModified(bool value);
void SetSessionTabCaptionTruncation(bool value);
void SetLoadingTooLongLimit(int value);
void SetFirstRun(const UnicodeString & value);
int __fastcall GetLocaleCompletenessTreshold();

Expand Down Expand Up @@ -812,6 +814,7 @@ class TWinConfiguration : public TCustomWinConfiguration
__property bool HiContrast = { read = FHiContrast, write = SetHiContrast };
__property bool EditorCheckNotModified = { read = FEditorCheckNotModified, write = SetEditorCheckNotModified };
__property bool SessionTabCaptionTruncation = { read = FSessionTabCaptionTruncation, write = SetSessionTabCaptionTruncation };
__property int LoadingTooLongLimit = { read = FLoadingTooLongLimit, write = SetLoadingTooLongLimit };
__property UnicodeString FirstRun = { read = FFirstRun, write = SetFirstRun };
__property LCID DefaultLocale = { read = FDefaultLocale };
__property int LocaleCompletenessTreshold = { read = GetLocaleCompletenessTreshold };
Expand Down

0 comments on commit e23d054

Please sign in to comment.