mirror of
https://github.com/CloudDelphi/Virtual-File-System
synced 2025-12-19 09:53:54 +01:00
Added live vfs watching/reloading support in debug mode
This commit is contained in:
parent
081194c185
commit
9e2c531428
@ -56,6 +56,7 @@ begin
|
|||||||
VfsBase.MapDir(RootDir, VfsUtils.MakePath([RootDir, 'Mods\Apache']), DONT_OVERWRITE_EXISTING);
|
VfsBase.MapDir(RootDir, VfsUtils.MakePath([RootDir, 'Mods\Apache']), DONT_OVERWRITE_EXISTING);
|
||||||
VfsDebug.SetLoggingProc(LogSomething);
|
VfsDebug.SetLoggingProc(LogSomething);
|
||||||
VfsControl.RunVfs(VfsBase.SORT_FIFO);
|
VfsControl.RunVfs(VfsBase.SORT_FIFO);
|
||||||
|
Windows.MessageBoxA(0, '', '', 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TestIntegrated.TearDown;
|
procedure TestIntegrated.TearDown;
|
||||||
|
|||||||
149
VfsBase.pas
149
VfsBase.pas
@ -76,6 +76,7 @@ type
|
|||||||
PrevDisableVfsForThisThread: boolean;
|
PrevDisableVfsForThisThread: boolean;
|
||||||
|
|
||||||
procedure DisableVfsForThread;
|
procedure DisableVfsForThread;
|
||||||
|
procedure EnableVfsForThread;
|
||||||
procedure RestoreVfsForThread;
|
procedure RestoreVfsForThread;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -98,6 +99,13 @@ function PauseVfs: LONGBOOL; stdcall;
|
|||||||
(* Stops VFS and clears all mappings *)
|
(* Stops VFS and clears all mappings *)
|
||||||
function ResetVfs: LONGBOOL; stdcall;
|
function ResetVfs: LONGBOOL; stdcall;
|
||||||
|
|
||||||
|
(* If VFS is running or paused, pauses VFS, clears cache and fully reaplies all mappings in the same order and
|
||||||
|
with the same arguments, as MapDir routines were called earlier. Restores VFS state afterwards *)
|
||||||
|
function RefreshVfs: LONGBOOL; stdcall;
|
||||||
|
|
||||||
|
(* Refreshes VFS item attributes info for given mapped file. File must exist to succeed *)
|
||||||
|
function RefreshMappedFile (const FilePath: WideString): boolean;
|
||||||
|
|
||||||
(* Returns true if VFS is active globally and for current thread *)
|
(* Returns true if VFS is active globally and for current thread *)
|
||||||
function IsVfsActive: boolean;
|
function IsVfsActive: boolean;
|
||||||
|
|
||||||
@ -122,17 +130,37 @@ function CallWithoutVfs (Func: TSingleArgExternalFunc; Arg: pointer = nil): inte
|
|||||||
(***) implementation (***)
|
(***) implementation (***)
|
||||||
|
|
||||||
|
|
||||||
|
type
|
||||||
|
(* Applied and remembered mapping. Used to refresh or report VFS *)
|
||||||
|
TMapping = class
|
||||||
|
AbsVirtPath: WideString;
|
||||||
|
AbsRealPath: WideString;
|
||||||
|
OverwriteExisting: boolean;
|
||||||
|
Flags: integer;
|
||||||
|
|
||||||
|
class function Make (const AbsVirtPath, AbsRealPath: WideString; OverwriteExisting: boolean; Flags: integer): TMapping;
|
||||||
|
end;
|
||||||
|
|
||||||
var
|
var
|
||||||
(*
|
(*
|
||||||
Global map of case-insensitive normalized path to file/directory => corresponding TVfsItem.
|
Global map of case-insensitive normalized path to file/directory => corresponding TVfsItem.
|
||||||
Access is controlled via critical section and global/thread switchers.
|
Access is controlled via critical section and global/thread switchers.
|
||||||
Represents the whole cached virtual file system contents.
|
Represents the whole cached virtual file system contents.
|
||||||
*)
|
*)
|
||||||
{O} VfsItems: {O} TDict {OF TVfsItem};
|
{O} VfsItems: {O} TDict {of TVfsItem};
|
||||||
|
|
||||||
|
(* Map of real (mapped) file path => VFS item. Used to update VFS info whenever mapped files are changed *)
|
||||||
|
{O} MappedFiles: {U} TDict {of TVfsItem};
|
||||||
|
|
||||||
|
(* List of all applied mappings *)
|
||||||
|
{O} Mappings: {O} TList {of TMapping};
|
||||||
|
|
||||||
(* Global VFS state indicator. If false, all VFS search operations must fail *)
|
(* Global VFS state indicator. If false, all VFS search operations must fail *)
|
||||||
VfsIsRunning: boolean = false;
|
VfsIsRunning: boolean = false;
|
||||||
|
|
||||||
|
(* Directory listing ordering, chosen on first VFS run. Updated on any first run after reset *)
|
||||||
|
VfsDirListingOrder: TDirListingSortType;
|
||||||
|
|
||||||
(* If true, VFS file/directory hierarchy is built and no mapping is allowed untill full reset *)
|
(* If true, VFS file/directory hierarchy is built and no mapping is allowed untill full reset *)
|
||||||
VfsTreeIsBuilt: boolean = false;
|
VfsTreeIsBuilt: boolean = false;
|
||||||
|
|
||||||
@ -144,6 +172,7 @@ var
|
|||||||
threadvar
|
threadvar
|
||||||
DisableVfsForThisThread: boolean;
|
DisableVfsForThisThread: boolean;
|
||||||
|
|
||||||
|
|
||||||
function TVfsItem.IsDir: boolean;
|
function TVfsItem.IsDir: boolean;
|
||||||
begin
|
begin
|
||||||
result := (Self.Info.Base.FileAttributes and Windows.FILE_ATTRIBUTE_DIRECTORY) <> 0;
|
result := (Self.Info.Base.FileAttributes and Windows.FILE_ATTRIBUTE_DIRECTORY) <> 0;
|
||||||
@ -171,6 +200,12 @@ begin
|
|||||||
DisableVfsForThisThread := true;
|
DisableVfsForThisThread := true;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TThreadVfsDisabler.EnableVfsForThread;
|
||||||
|
begin
|
||||||
|
Self.PrevDisableVfsForThisThread := DisableVfsForThisThread;
|
||||||
|
DisableVfsForThisThread := false;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TThreadVfsDisabler.RestoreVfsForThread;
|
procedure TThreadVfsDisabler.RestoreVfsForThread;
|
||||||
begin
|
begin
|
||||||
DisableVfsForThisThread := Self.PrevDisableVfsForThisThread;
|
DisableVfsForThisThread := Self.PrevDisableVfsForThisThread;
|
||||||
@ -297,6 +332,15 @@ begin
|
|||||||
end;
|
end;
|
||||||
end; // .procedure BuildVfsItemsTree
|
end; // .procedure BuildVfsItemsTree
|
||||||
|
|
||||||
|
class function TMapping.Make (const AbsVirtPath, AbsRealPath: WideString; OverwriteExisting: boolean; Flags: integer): {O} TMapping;
|
||||||
|
begin
|
||||||
|
result := TMapping.Create;
|
||||||
|
result.AbsVirtPath := AbsVirtPath;
|
||||||
|
result.AbsRealPath := AbsRealPath;
|
||||||
|
result.OverwriteExisting := OverwriteExisting;
|
||||||
|
result.Flags := Flags;
|
||||||
|
end;
|
||||||
|
|
||||||
function RunVfs (DirListingOrder: TDirListingSortType): boolean;
|
function RunVfs (DirListingOrder: TDirListingSortType): boolean;
|
||||||
begin
|
begin
|
||||||
result := not DisableVfsForThisThread;
|
result := not DisableVfsForThisThread;
|
||||||
@ -307,6 +351,7 @@ begin
|
|||||||
|
|
||||||
if not VfsIsRunning then begin
|
if not VfsIsRunning then begin
|
||||||
if not VfsTreeIsBuilt then begin
|
if not VfsTreeIsBuilt then begin
|
||||||
|
VfsDirListingOrder := DirListingOrder;
|
||||||
BuildVfsItemsTree();
|
BuildVfsItemsTree();
|
||||||
SortVfsDirListings(DirListingOrder);
|
SortVfsDirListings(DirListingOrder);
|
||||||
VfsTreeIsBuilt := true;
|
VfsTreeIsBuilt := true;
|
||||||
@ -340,7 +385,9 @@ begin
|
|||||||
if result then begin
|
if result then begin
|
||||||
with VfsCritSection do begin
|
with VfsCritSection do begin
|
||||||
Enter;
|
Enter;
|
||||||
VfsItems.Clear();
|
VfsItems.Clear;
|
||||||
|
MappedFiles.Clear;
|
||||||
|
Mappings.Clear;
|
||||||
VfsIsRunning := false;
|
VfsIsRunning := false;
|
||||||
VfsTreeIsBuilt := false;
|
VfsTreeIsBuilt := false;
|
||||||
Leave;
|
Leave;
|
||||||
@ -472,6 +519,7 @@ begin
|
|||||||
VfsItem.RealPath := AbsRealPath;
|
VfsItem.RealPath := AbsRealPath;
|
||||||
VfsItem.Priority := Priority;
|
VfsItem.Priority := Priority;
|
||||||
VfsItem.Attrs := 0;
|
VfsItem.Attrs := 0;
|
||||||
|
MappedFiles[WideStrToCaselessKey(AbsRealPath)] := VfsItem;
|
||||||
end; // .if
|
end; // .if
|
||||||
end; // .if
|
end; // .if
|
||||||
|
|
||||||
@ -480,7 +528,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end; // .function RedirectFile
|
end; // .function RedirectFile
|
||||||
|
|
||||||
function _MapDir (const AbsVirtPath, AbsRealPath: WideString; {n} FileInfoPtr: WinNative.PFILE_ID_BOTH_DIR_INFORMATION; OverwriteExisting: boolean; Priority: integer): {Un} TVfsItem;
|
function _MapDir (const AbsVirtPath, AbsRealPath: WideString; {n} FileInfoPtr: WinNative.PFILE_ID_BOTH_DIR_INFORMATION; OverwriteExisting: boolean; Flags, Priority: integer): {Un} TVfsItem;
|
||||||
var
|
var
|
||||||
{O} Subdirs: {O} TList {OF TFileInfo};
|
{O} Subdirs: {O} TList {OF TFileInfo};
|
||||||
{U} SubdirInfo: TFileInfo;
|
{U} SubdirInfo: TFileInfo;
|
||||||
@ -532,7 +580,7 @@ begin
|
|||||||
|
|
||||||
for i := 0 to Subdirs.Count - 1 do begin
|
for i := 0 to Subdirs.Count - 1 do begin
|
||||||
SubdirInfo := TFileInfo(Subdirs[i]);
|
SubdirInfo := TFileInfo(Subdirs[i]);
|
||||||
_MapDir(VirtPathPrefix + SubdirInfo.Data.FileName, RealPathPrefix + SubdirInfo.Data.FileName, @SubdirInfo.Data, OverwriteExisting, Priority);
|
_MapDir(VirtPathPrefix + SubdirInfo.Data.FileName, RealPathPrefix + SubdirInfo.Data.FileName, @SubdirInfo.Data, OverwriteExisting, Flags, Priority);
|
||||||
end;
|
end;
|
||||||
end; // .if
|
end; // .if
|
||||||
|
|
||||||
@ -544,14 +592,26 @@ begin
|
|||||||
end; // .function _MapDir
|
end; // .function _MapDir
|
||||||
|
|
||||||
function MapDir (const VirtPath, RealPath: WideString; OverwriteExisting: boolean; Flags: integer = 0): boolean;
|
function MapDir (const VirtPath, RealPath: WideString; OverwriteExisting: boolean; Flags: integer = 0): boolean;
|
||||||
|
var
|
||||||
|
AbsVirtPath: WideString;
|
||||||
|
AbsRealPath: WideString;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
result := EnterVfsConfig;
|
result := EnterVfsConfig;
|
||||||
|
|
||||||
if result then begin
|
if result then begin
|
||||||
result := _MapDir(NormalizePath(VirtPath), NormalizePath(RealPath), nil, OverwriteExisting, AUTO_PRIORITY) <> nil;
|
AbsVirtPath := VfsUtils.NormalizePath(VirtPath);
|
||||||
|
AbsRealPath := VfsUtils.NormalizePath(RealPath);
|
||||||
|
result := (AbsVirtPath <> '') and (AbsRealPath <> '');
|
||||||
|
|
||||||
|
if result then begin
|
||||||
|
Mappings.Add(TMapping.Make(AbsVirtPath, AbsRealPath, OverwriteExisting, Flags));
|
||||||
|
result := _MapDir(AbsVirtPath, AbsRealPath, nil, OverwriteExisting, Flags, AUTO_PRIORITY) <> nil;
|
||||||
|
end;
|
||||||
|
|
||||||
LeaveVfsConfig;
|
LeaveVfsConfig;
|
||||||
end;
|
end;
|
||||||
end;
|
end; // .function MapDir
|
||||||
|
|
||||||
function CallWithoutVfs (Func: TSingleArgExternalFunc; Arg: pointer = nil): integer; stdcall;
|
function CallWithoutVfs (Func: TSingleArgExternalFunc; Arg: pointer = nil): integer; stdcall;
|
||||||
begin
|
begin
|
||||||
@ -565,7 +625,84 @@ begin
|
|||||||
end;
|
end;
|
||||||
end; // .function CallWithoutVfs
|
end; // .function CallWithoutVfs
|
||||||
|
|
||||||
|
function RefreshVfs: LONGBOOL; stdcall;
|
||||||
|
var
|
||||||
|
VfsWasRunning: boolean;
|
||||||
|
i: integer;
|
||||||
|
|
||||||
|
begin
|
||||||
|
result := not DisableVfsForThisThread;
|
||||||
|
|
||||||
|
if result then begin
|
||||||
|
with VfsCritSection do begin
|
||||||
|
Enter;
|
||||||
|
result := VfsTreeIsBuilt;
|
||||||
|
|
||||||
|
if result then begin
|
||||||
|
VfsItems.Clear;
|
||||||
|
MappedFiles.Clear;
|
||||||
|
VfsWasRunning := VfsIsRunning;
|
||||||
|
VfsIsRunning := false;
|
||||||
|
VfsTreeIsBuilt := false;
|
||||||
|
|
||||||
|
for i := 0 to Mappings.Count - 1 do begin
|
||||||
|
with TMapping(Mappings[i]) do begin
|
||||||
|
MapDir(AbsVirtPath, AbsRealPath, OverwriteExisting, Flags);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if VfsWasRunning then begin
|
||||||
|
BuildVfsItemsTree();
|
||||||
|
SortVfsDirListings(VfsDirListingOrder);
|
||||||
|
VfsTreeIsBuilt := true;
|
||||||
|
VfsIsRunning := true;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Leave;
|
||||||
|
end; // .with
|
||||||
|
end; // .if
|
||||||
|
end; // .function RefreshVfs
|
||||||
|
|
||||||
|
function RefreshMappedFile (const FilePath: WideString): boolean;
|
||||||
|
var
|
||||||
|
{U} VfsItem: TVfsItem;
|
||||||
|
AbsRealPath: WideString;
|
||||||
|
FileInfo: TNativeFileInfo;
|
||||||
|
VfsWasRunning: boolean;
|
||||||
|
|
||||||
|
begin
|
||||||
|
VfsItem := nil;
|
||||||
|
// * * * * * //
|
||||||
|
result := not DisableVfsForThisThread;
|
||||||
|
|
||||||
|
if result then begin
|
||||||
|
with VfsCritSection do begin
|
||||||
|
Enter;
|
||||||
|
result := VfsTreeIsBuilt;
|
||||||
|
|
||||||
|
if result then begin
|
||||||
|
VfsWasRunning := VfsIsRunning;
|
||||||
|
VfsIsRunning := false;
|
||||||
|
AbsRealPath := NormalizePath(FilePath);
|
||||||
|
VfsItem := TVfsItem(MappedFiles[WideStrToCaselessKey(AbsRealPath)]);
|
||||||
|
result := (VfsItem <> nil) and GetFileInfo(AbsRealPath, FileInfo);
|
||||||
|
|
||||||
|
if result then begin
|
||||||
|
CopyFileInfoWithoutNames(FileInfo.Base, VfsItem.Info.Base);
|
||||||
|
end;
|
||||||
|
|
||||||
|
VfsIsRunning := VfsWasRunning;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Leave;
|
||||||
|
end; // .with
|
||||||
|
end; // .if
|
||||||
|
end; // .function RefreshMappedFile
|
||||||
|
|
||||||
begin
|
begin
|
||||||
VfsCritSection.Init;
|
VfsCritSection.Init;
|
||||||
VfsItems := DataLib.NewDict(Utils.OWNS_ITEMS, DataLib.CASE_SENSITIVE);
|
VfsItems := DataLib.NewDict(Utils.OWNS_ITEMS, DataLib.CASE_SENSITIVE);
|
||||||
|
MappedFiles := DataLib.NewDict(not Utils.OWNS_ITEMS, DataLib.CASE_SENSITIVE);
|
||||||
|
Mappings := DataLib.NewList(Utils.OWNS_ITEMS);
|
||||||
end.
|
end.
|
||||||
@ -8,9 +8,8 @@ unit VfsControl;
|
|||||||
|
|
||||||
uses
|
uses
|
||||||
Windows, SysUtils,
|
Windows, SysUtils,
|
||||||
Utils, WinUtils, TypeWrappers, DataLib,
|
Utils, WinUtils, TypeWrappers, DataLib, Files, StrLib,
|
||||||
Files, StrLib,
|
VfsBase, VfsUtils, VfsHooks, VfsWatching, DlgMes, FilesEx {FIXME DELETEME};
|
||||||
VfsBase, VfsUtils, VfsHooks, DlgMes {FIXME DELETEME};
|
|
||||||
|
|
||||||
type
|
type
|
||||||
(* Import *)
|
(* Import *)
|
||||||
@ -23,10 +22,11 @@ function RunVfs (DirListingOrder: VfsBase.TDirListingSortType): LONGBOOL; stdcal
|
|||||||
(* Loads mod list from file and maps each mod directory to specified root directory.
|
(* Loads mod list from file and maps each mod directory to specified root directory.
|
||||||
File with mod list is treated as (BOM or BOM-less) UTF-8 plain text file, where each mod name is separated
|
File with mod list is treated as (BOM or BOM-less) UTF-8 plain text file, where each mod name is separated
|
||||||
from another one via Line Feed (#10) character. Each mod named is trimmed, converted to UCS16 and validated before
|
from another one via Line Feed (#10) character. Each mod named is trimmed, converted to UCS16 and validated before
|
||||||
adding to list. Invalid or empty mods will be skipped.
|
adding to list. Invalid or empty mods will be skipped. Mods are mapped in reverse order, as compared to their order in file.
|
||||||
Returns true if root and mods directory existed and file with mod list was loaded successfully *)
|
Returns true if root and mods directory existed and file with mod list was loaded successfully *)
|
||||||
function MapModsFromList (const RootDir, ModsDir, ModListFile: WideString; Flags: integer = 0): boolean;
|
function MapModsFromList (const RootDir, ModsDir, ModListFile: WideString; Flags: integer = 0): boolean;
|
||||||
|
|
||||||
|
|
||||||
(***) implementation (***)
|
(***) implementation (***)
|
||||||
|
|
||||||
|
|
||||||
@ -83,7 +83,6 @@ const
|
|||||||
UTF8_BOM = #$EF#$BB#$BF;
|
UTF8_BOM = #$EF#$BB#$BF;
|
||||||
|
|
||||||
var
|
var
|
||||||
{O} OpenedFile: Files.TFile;
|
|
||||||
AbsFilePath: WideString;
|
AbsFilePath: WideString;
|
||||||
FileHandle: integer;
|
FileHandle: integer;
|
||||||
FileContents: string;
|
FileContents: string;
|
||||||
@ -93,8 +92,6 @@ var
|
|||||||
i: integer;
|
i: integer;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
OpenedFile := Files.TFile.Create;
|
|
||||||
// * * * * * //
|
|
||||||
AbsFilePath := VfsUtils.NormalizePath(ModListFilePath);
|
AbsFilePath := VfsUtils.NormalizePath(ModListFilePath);
|
||||||
FileHandle := integer(Windows.INVALID_HANDLE_VALUE);
|
FileHandle := integer(Windows.INVALID_HANDLE_VALUE);
|
||||||
result := AbsFilePath <> '';
|
result := AbsFilePath <> '';
|
||||||
@ -129,8 +126,6 @@ begin
|
|||||||
|
|
||||||
Windows.CloseHandle(FileHandle);
|
Windows.CloseHandle(FileHandle);
|
||||||
end; // .if
|
end; // .if
|
||||||
// * * * * * //
|
|
||||||
SysUtils.FreeAndNil(OpenedFile);
|
|
||||||
end; // .function LoadModList
|
end; // .function LoadModList
|
||||||
|
|
||||||
function MapModsFromList_ (const RootDir, ModsDir: WideString; ModList: TModList; Flags: integer = 0): boolean;
|
function MapModsFromList_ (const RootDir, ModsDir: WideString; ModList: TModList; Flags: integer = 0): boolean;
|
||||||
@ -181,4 +176,18 @@ begin
|
|||||||
SysUtils.FreeAndNil(ModList);
|
SysUtils.FreeAndNil(ModList);
|
||||||
end; // .function MapModsFromList
|
end; // .function MapModsFromList
|
||||||
|
|
||||||
|
var s: string;
|
||||||
|
begin
|
||||||
|
// MapModsFromList('D:\Heroes 3', 'D:\heroes 3\Mods', 'd:\heroes 3\mods\list.txt');
|
||||||
|
// RunVfs(SORT_FIFO);
|
||||||
|
// ReadFileContents('D:\heroes 3\data\s\__T.erm', s);
|
||||||
|
// s := copy(s, 1, 100);
|
||||||
|
// VarDump([s]);
|
||||||
|
// VfsBase.PauseVfs;
|
||||||
|
// VfsBase.RefreshVfs;
|
||||||
|
// VfsBase.RunVfs(SORT_FIFO);
|
||||||
|
// ReadFileContents('D:\heroes 3\data\s\__T.erm', s);
|
||||||
|
// s := copy(s, 1, 100);
|
||||||
|
// VarDump([s]);
|
||||||
|
// exit;
|
||||||
end.
|
end.
|
||||||
@ -16,6 +16,7 @@ exports
|
|||||||
VfsControl.RunVfs,
|
VfsControl.RunVfs,
|
||||||
VfsBase.PauseVfs,
|
VfsBase.PauseVfs,
|
||||||
VfsBase.ResetVfs,
|
VfsBase.ResetVfs,
|
||||||
|
VfsBase.RefreshVfs,
|
||||||
VfsBase.CallWithoutVfs;
|
VfsBase.CallWithoutVfs;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<Version>7.0</Version>
|
<Version>7.0</Version>
|
||||||
<DCC_Define>DEBUG</DCC_Define>
|
|
||||||
<DCC_IOChecking>False</DCC_IOChecking>
|
<DCC_IOChecking>False</DCC_IOChecking>
|
||||||
<DCC_UnitSearchPath>..\Lib\B2;.\Tests</DCC_UnitSearchPath>
|
<DCC_UnitSearchPath>..\Lib\B2;.\Tests</DCC_UnitSearchPath>
|
||||||
<DCC_ResourcePath>..\Lib\B2;.\Tests</DCC_ResourcePath>
|
<DCC_ResourcePath>..\Lib\B2;.\Tests</DCC_ResourcePath>
|
||||||
@ -42,38 +41,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Excluded_Packages Name="$(BDS)\bin\dcloffice2k100.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
|
<Excluded_Packages Name="$(BDS)\bin\dcloffice2k100.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||||
<Excluded_Packages Name="$(BDS)\bin\dclofficexp100.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
|
<Excluded_Packages Name="$(BDS)\bin\dclofficexp100.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||||
</Excluded_Packages></Delphi.Personality></BorlandProject></BorlandProject>
|
</Excluded_Packages></Delphi.Personality></BorlandProject></BorlandProject>
|
||||||
|
|||||||
373
VfsWatching.pas
Normal file
373
VfsWatching.pas
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
unit VfsWatching;
|
||||||
|
(*
|
||||||
|
Description: provides means to watch for mapped directories changes and refresh VFS.
|
||||||
|
Works unreliably when trying to watch the whole logical drive.
|
||||||
|
*)
|
||||||
|
|
||||||
|
|
||||||
|
(***) interface (***)
|
||||||
|
|
||||||
|
uses
|
||||||
|
Windows, SysUtils, Math,
|
||||||
|
Utils, Concur, WinUtils, StrLib, WinNative,
|
||||||
|
VfsBase, VfsUtils, DlgMes, Files {FIXME DELETEME};
|
||||||
|
|
||||||
|
|
||||||
|
(***) implementation (***)
|
||||||
|
|
||||||
|
|
||||||
|
type
|
||||||
|
(* Import *)
|
||||||
|
THandle = Windows.THandle;
|
||||||
|
|
||||||
|
const
|
||||||
|
(* Import *)
|
||||||
|
INVALID_HANDLE_VALUE = Windows.INVALID_HANDLE_VALUE;
|
||||||
|
|
||||||
|
STOP_EVENT_HANDLE_IND = 0;
|
||||||
|
NOTIFICATION_HANDLE_INDEX = 1;
|
||||||
|
NUM_WATCHED_HANDLES = 2;
|
||||||
|
|
||||||
|
type
|
||||||
|
TDirChangeAction = (NOTIFY_FILE_ADDED, NOTIFY_FILE_REMOVED, NOTIFY_FILE_MODIFIED, NOTIFY_FILE_RENAMED_FROM_NAME, NOTIFY_FILE_RENAMED_TO_NAME,
|
||||||
|
NOTIFY_STOP_EVENT, NOTIFY_TIMEOUT, NOTIFY_TOO_MANY_CHANGES, NOTIFY_UNKNOWN_ACTION);
|
||||||
|
TDirChangeFilter = set of TDirChangeAction;
|
||||||
|
|
||||||
|
const
|
||||||
|
NOTIFY_ALL = [NOTIFY_FILE_ADDED, NOTIFY_FILE_REMOVED, NOTIFY_FILE_MODIFIED, NOTIFY_FILE_RENAMED_FROM_NAME, NOTIFY_FILE_RENAMED_TO_NAME];
|
||||||
|
NO_STOP_EVENT = 0;
|
||||||
|
INFINITE = Windows.INFINITE;
|
||||||
|
|
||||||
|
type
|
||||||
|
(* Directory change record *)
|
||||||
|
TDirChange = record
|
||||||
|
Action: TDirChangeAction;
|
||||||
|
|
||||||
|
(* Absolute expanded and normalized path to file, that triggered notification *)
|
||||||
|
FilePath: WideString;
|
||||||
|
end;
|
||||||
|
|
||||||
|
IDirChangesIterator = interface
|
||||||
|
function IterNext ({out} var DirChange: TDirChange; StopEvent: THandle = 0; Timeout: integer = integer(Windows.INFINITE); NotifyFilter: TDirChangeFilter = NOTIFY_ALL): boolean;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TDirChangesIterator = class (Utils.TManagedObject, IDirChangesIterator)
|
||||||
|
protected const
|
||||||
|
BUF_SIZE = 65500;
|
||||||
|
|
||||||
|
protected
|
||||||
|
{O} fDirHandle: THandle;
|
||||||
|
{O} fNotifyEvent: THandle;
|
||||||
|
fDirPath: WideString;
|
||||||
|
fBuf: array [0..BUF_SIZE - 1] of byte;
|
||||||
|
fBufSize: integer;
|
||||||
|
fBufPos: integer;
|
||||||
|
fIsEnd: boolean;
|
||||||
|
|
||||||
|
public
|
||||||
|
constructor Create (const DirPath: WideString); overload;
|
||||||
|
destructor Destroy; override;
|
||||||
|
|
||||||
|
function IterNext ({out} var DirChange: TDirChange; StopEvent: THandle = 0; Timeout: integer = integer(Windows.INFINITE); NotifyFilter: TDirChangeFilter = NOTIFY_ALL): boolean;
|
||||||
|
end; // .class TDirChangesIterator
|
||||||
|
|
||||||
|
var
|
||||||
|
WatcherCritSection: Concur.TCritSection;
|
||||||
|
AbsWatcherDir: WideString;
|
||||||
|
WatcherDebounceInterval: integer;
|
||||||
|
WatcherStopEvent: THandle;
|
||||||
|
WatcherIsRunning: boolean;
|
||||||
|
WatcherThreadHandle: THandle;
|
||||||
|
WatcherThreadId: cardinal;
|
||||||
|
|
||||||
|
|
||||||
|
function IsValidHandle (Handle: THandle): boolean; inline;
|
||||||
|
begin
|
||||||
|
result := (Handle <> 0) and (Handle <> INVALID_HANDLE_VALUE);
|
||||||
|
end;
|
||||||
|
|
||||||
|
constructor TDirChangesIterator.Create (const DirPath: WideString);
|
||||||
|
const
|
||||||
|
MANUAL_RESET_EVENT = true;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Self.fDirPath := VfsUtils.NormalizePath(DirPath);
|
||||||
|
Self.fDirHandle := Windows.CreateFileW(PWideChar(Self.fDirPath), Windows.GENERIC_READ, Windows.FILE_SHARE_READ or Windows.FILE_SHARE_WRITE, nil,
|
||||||
|
Windows.OPEN_EXISTING, Windows.FILE_FLAG_BACKUP_SEMANTICS or Windows.FILE_FLAG_OVERLAPPED, 0);
|
||||||
|
|
||||||
|
if IsValidHandle(Self.fDirHandle) then begin
|
||||||
|
Self.fNotifyEvent := Windows.CreateEventW(nil, MANUAL_RESET_EVENT, false, nil);
|
||||||
|
|
||||||
|
if not IsValidHandle(Self.fNotifyEvent) then begin
|
||||||
|
Windows.CloseHandle(Self.fDirHandle);
|
||||||
|
Self.fDirHandle := 0;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Self.fIsEnd := not IsValidHandle(Self.fDirHandle);
|
||||||
|
end; // .constructor TDirChangesIterator.Create
|
||||||
|
|
||||||
|
destructor TDirChangesIterator.Destroy;
|
||||||
|
begin
|
||||||
|
if IsValidHandle(Self.fDirHandle) then begin
|
||||||
|
Windows.CloseHandle(Self.fDirHandle);
|
||||||
|
end;
|
||||||
|
|
||||||
|
if IsValidHandle(Self.fNotifyEvent) then begin
|
||||||
|
Windows.CloseHandle(Self.fNotifyEvent);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function DecodeNativeDirChangeAction (Action: integer): TDirChangeAction;
|
||||||
|
begin
|
||||||
|
case Action of
|
||||||
|
Windows.FILE_ACTION_ADDED: result := NOTIFY_FILE_ADDED;
|
||||||
|
Windows.FILE_ACTION_REMOVED: result := NOTIFY_FILE_REMOVED;
|
||||||
|
Windows.FILE_ACTION_MODIFIED: result := NOTIFY_FILE_MODIFIED;
|
||||||
|
Windows.FILE_ACTION_RENAMED_OLD_NAME: result := NOTIFY_FILE_RENAMED_FROM_NAME;
|
||||||
|
Windows.FILE_ACTION_RENAMED_NEW_NAME: result := NOTIFY_FILE_RENAMED_TO_NAME;
|
||||||
|
else
|
||||||
|
result := NOTIFY_UNKNOWN_ACTION;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function DirChangeFilterToNative (Filter: TDirChangeFilter): integer;
|
||||||
|
begin
|
||||||
|
result := 0;
|
||||||
|
|
||||||
|
if NOTIFY_FILE_ADDED in Filter then result := result or Windows.FILE_ACTION_ADDED;
|
||||||
|
if NOTIFY_FILE_REMOVED in Filter then result := result or Windows.FILE_ACTION_REMOVED;
|
||||||
|
if NOTIFY_FILE_MODIFIED in Filter then result := result or Windows.FILE_ACTION_MODIFIED;
|
||||||
|
if NOTIFY_FILE_RENAMED_FROM_NAME in Filter then result := result or Windows.FILE_ACTION_RENAMED_OLD_NAME;
|
||||||
|
if NOTIFY_FILE_RENAMED_TO_NAME in Filter then result := result or Windows.FILE_ACTION_RENAMED_NEW_NAME;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDirChangesIterator.IterNext ({out} var DirChange: TDirChange; StopEvent: THandle = 0; Timeout: integer = integer(Windows.INFINITE); NotifyFilter: TDirChangeFilter = NOTIFY_ALL): boolean;
|
||||||
|
const
|
||||||
|
WATCH_SUBTREE = true;
|
||||||
|
WAIT_OVERLAPPED = true;
|
||||||
|
|
||||||
|
|
||||||
|
var
|
||||||
|
{n} NotifInfoInBuf: WinNative.PFILE_NOTIFY_INFORMATION;
|
||||||
|
AsyncRes: Windows.TOverlapped;
|
||||||
|
TriggeredEvent: THandle;
|
||||||
|
Dummy: integer;
|
||||||
|
|
||||||
|
begin
|
||||||
|
NotifInfoInBuf := nil;
|
||||||
|
// * * * * * //
|
||||||
|
result := not Self.fIsEnd;
|
||||||
|
|
||||||
|
if not result then begin
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if Timeout = 0 then begin
|
||||||
|
DirChange.Action := NOTIFY_TIMEOUT;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if Self.fBufPos < fBufSize then begin
|
||||||
|
NotifInfoInBuf := @Self.fBuf[Self.fBufPos];
|
||||||
|
DirChange.Action := DecodeNativeDirChangeAction(NotifInfoInBuf.Action);
|
||||||
|
|
||||||
|
if DirChange.Action = NOTIFY_FILE_REMOVED then begin
|
||||||
|
DirChange.FilePath := VfsUtils.AddBackslash(Self.fDirPath) + NotifInfoInBuf.GetFileName;
|
||||||
|
DirChange.FilePath := VfsUtils.AddBackslash(WinUtils.GetLongPathW(StrLib.ExtractDirPathW(DirChange.FilePath))) + StrLib.ExtractFileNameW(DirChange.FilePath);
|
||||||
|
end else begin
|
||||||
|
DirChange.FilePath := WinUtils.GetLongPathW(VfsUtils.AddBackslash(Self.fDirPath) + NotifInfoInBuf.GetFileName);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Self.fBufPos := Utils.IfThen(NotifInfoInBuf.NextEntryOffset <> 0, Self.fBufPos + integer(NotifInfoInBuf.NextEntryOffset), Self.BUF_SIZE);
|
||||||
|
end else begin
|
||||||
|
FillChar(AsyncRes, sizeof(AsyncRes), 0);
|
||||||
|
AsyncRes.hEvent := Self.fNotifyEvent;
|
||||||
|
Windows.ResetEvent(Self.fNotifyEvent);
|
||||||
|
|
||||||
|
Self.fBufSize := 0;
|
||||||
|
Self.fBufPos := 0;
|
||||||
|
result := Windows.ReadDirectoryChangesW(Self.fDirHandle, @Self.fBuf, sizeof(Self.fBuf), WATCH_SUBTREE, DirChangeFilterToNative(NotifyFilter), @Dummy, @AsyncRes, nil);
|
||||||
|
|
||||||
|
if result then begin
|
||||||
|
DirChange.FilePath := '';
|
||||||
|
|
||||||
|
case WinUtils.WaitForObjects([StopEvent, Self.fNotifyEvent], TriggeredEvent, Timeout) of
|
||||||
|
WinUtils.WR_WAITED: begin
|
||||||
|
if TriggeredEvent = StopEvent then begin
|
||||||
|
DirChange.Action := NOTIFY_STOP_EVENT;
|
||||||
|
end else begin
|
||||||
|
result := Windows.GetOverlappedResult(Self.fNotifyEvent, AsyncRes, cardinal(Self.fBufSize), not WAIT_OVERLAPPED);
|
||||||
|
|
||||||
|
if result then begin
|
||||||
|
if Self.fBufSize = 0 then begin
|
||||||
|
DirChange.Action := NOTIFY_TOO_MANY_CHANGES;
|
||||||
|
end else if Self.fBufSize < sizeof(NotifInfoInBuf^) + sizeof(WideChar) then begin
|
||||||
|
result := false;
|
||||||
|
end else begin
|
||||||
|
result := Self.IterNext(DirChange, StopEvent, Timeout, NotifyFilter);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end; // .case WR_WAITED
|
||||||
|
|
||||||
|
WinUtils.WR_TIMEOUT: begin
|
||||||
|
DirChange.Action := NOTIFY_TIMEOUT;
|
||||||
|
end;
|
||||||
|
else
|
||||||
|
result := false;
|
||||||
|
end; // .switch wait result
|
||||||
|
end; // .if
|
||||||
|
|
||||||
|
Self.fIsEnd := not result;
|
||||||
|
end; // .else
|
||||||
|
end; // .function TDirChangesIterator.IterNext
|
||||||
|
|
||||||
|
function ReadDirChanges (const DirPath: WideString): IDirChangesIterator;
|
||||||
|
begin
|
||||||
|
result := TDirChangesIterator.Create(DirPath);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// function ReadNotification (const DirPath: WideString; var NotificationHandle: THandle): boolean;
|
||||||
|
// begin
|
||||||
|
// if IsValidHandle(NotificationHandle) then begin
|
||||||
|
// result := FindNextChangeNotification(NotificationHandle);
|
||||||
|
|
||||||
|
// if not result then begin
|
||||||
|
// Windows.FindCloseChangeNotification(NotificationHandle);
|
||||||
|
// NotificationHandle := INVALID_HANDLE_VALUE;
|
||||||
|
// end;
|
||||||
|
// end else begin
|
||||||
|
// NotificationHandle := FindFirstChangeNotificationW(PWideChar(DirPath), true, FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME or FILE_NOTIFY_CHANGE_ATTRIBUTES or
|
||||||
|
// FILE_NOTIFY_CHANGE_SIZE or FILE_NOTIFY_CHANGE_LAST_WRITE);
|
||||||
|
// result := IsValidHandle(NotificationHandle);
|
||||||
|
// end;
|
||||||
|
// end; // .function ReadNotification
|
||||||
|
|
||||||
|
function WatcherThreadProc (Arg: integer): integer; stdcall;
|
||||||
|
var
|
||||||
|
IsEnd: LONGBOOL;
|
||||||
|
NeedFullRescan: LONGBOOL;
|
||||||
|
CurrentTime: Int64;
|
||||||
|
LastChangeTime: Int64;
|
||||||
|
PlannedRescanTime: Int64;
|
||||||
|
Timeout: integer;
|
||||||
|
DummyEvent: THandle;
|
||||||
|
DirChangesScanner: IDirChangesIterator;
|
||||||
|
DirChange: TDirChange;
|
||||||
|
|
||||||
|
filesize: integer;
|
||||||
|
|
||||||
|
begin
|
||||||
|
DirChangesScanner := nil;
|
||||||
|
// * * * * * //
|
||||||
|
IsEnd := false;
|
||||||
|
NeedFullRescan := false;
|
||||||
|
LastChangeTime := 0;
|
||||||
|
result := 0;
|
||||||
|
|
||||||
|
with VfsBase.GetThreadVfsDisabler do begin
|
||||||
|
DisableVfsForThread;
|
||||||
|
|
||||||
|
try
|
||||||
|
while not IsEnd do begin
|
||||||
|
CurrentTime := GetMicroTime;
|
||||||
|
PlannedRescanTime := LastChangeTime + Int64(WatcherDebounceInterval);
|
||||||
|
|
||||||
|
if NeedFullRescan and (PlannedRescanTime <= CurrentTime) then begin
|
||||||
|
VfsBase.RefreshVfs;
|
||||||
|
NeedFullRescan := false;
|
||||||
|
VarDump(['Rescaned']);
|
||||||
|
end;
|
||||||
|
|
||||||
|
if DirChangesScanner = nil then begin
|
||||||
|
DirChangesScanner := TDirChangesIterator.Create(AbsWatcherDir);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Failed to start watching directory
|
||||||
|
if not DirChangesScanner.IterNext(DirChange, WatcherStopEvent, Utils.IfThen(boolean(NeedFullRescan), integer(PlannedRescanTime - CurrentTime), integer(Windows.INFINITE))) then begin
|
||||||
|
VarDump([':(', AbsWatcherDir]);
|
||||||
|
// Force scanner recreation later
|
||||||
|
DirChangesScanner := nil;
|
||||||
|
|
||||||
|
// Wait and retry, unless stop signal is received
|
||||||
|
Timeout := Utils.IfThen(NeedFullRescan, Min(WatcherDebounceInterval, integer(PlannedRescanTime - CurrentTime)), WatcherDebounceInterval);
|
||||||
|
|
||||||
|
if WinUtils.WaitForObjects([WatcherStopEvent], DummyEvent, Timeout) = WinUtils.WR_WAITED then begin
|
||||||
|
IsEnd := true;
|
||||||
|
end;
|
||||||
|
// Ok, got some signal
|
||||||
|
end else begin
|
||||||
|
VarDump([ord(DirChange.Action), DirChange.FilePath]);
|
||||||
|
if DirChange.Action = NOTIFY_STOP_EVENT then begin
|
||||||
|
IsEnd := true;
|
||||||
|
end else if DirChange.Action = NOTIFY_TIMEOUT then begin
|
||||||
|
// Will perform full rescan on next loop iteration
|
||||||
|
end else if DirChange.Action in [NOTIFY_FILE_ADDED, NOTIFY_FILE_REMOVED, NOTIFY_FILE_RENAMED_FROM_NAME, NOTIFY_FILE_RENAMED_TO_NAME, NOTIFY_UNKNOWN_ACTION, NOTIFY_TOO_MANY_CHANGES] then begin
|
||||||
|
LastChangeTime := WinUtils.GetMicroTime;
|
||||||
|
NeedFullRescan := true;
|
||||||
|
end else if DirChange.Action = NOTIFY_FILE_MODIFIED then begin
|
||||||
|
if not NeedFullRescan then begin
|
||||||
|
VfsBase.RefreshMappedFile(DirChange.FilePath);
|
||||||
|
with VfsBase.GetThreadVfsDisabler do begin
|
||||||
|
EnableVfsForThread;
|
||||||
|
Files.GetFileSize(DirChange.FilePath, FileSize);
|
||||||
|
RestoreVfsForThread;
|
||||||
|
end;
|
||||||
|
|
||||||
|
VarDump([DirChange.FilePath, 'New size is', FileSize], 'Updated');
|
||||||
|
end;
|
||||||
|
|
||||||
|
LastChangeTime := WinUtils.GetMicroTime;
|
||||||
|
end;
|
||||||
|
end; // .else
|
||||||
|
end; // .while
|
||||||
|
finally
|
||||||
|
RestoreVfsForThread;
|
||||||
|
end; // .try
|
||||||
|
end; // .with
|
||||||
|
end; // .function WatcherThreadProc
|
||||||
|
|
||||||
|
function RunWatcher (const WatchDir: WideString; DebounceInterval: integer): boolean;
|
||||||
|
const
|
||||||
|
MANUAL_RESET = true;
|
||||||
|
|
||||||
|
begin
|
||||||
|
with WatcherCritSection do begin
|
||||||
|
Enter;
|
||||||
|
|
||||||
|
result := not WatcherIsRunning;
|
||||||
|
|
||||||
|
if result then begin
|
||||||
|
AbsWatcherDir := VfsUtils.NormalizePath(WatchDir);
|
||||||
|
WatcherDebounceInterval := Max(0, DebounceInterval);
|
||||||
|
|
||||||
|
if not WinUtils.IsValidHandle(WatcherStopEvent) then begin
|
||||||
|
WatcherStopEvent := Windows.CreateEventW(nil, MANUAL_RESET, false, nil);
|
||||||
|
result := WinUtils.IsValidHandle(WatcherStopEvent);
|
||||||
|
end;
|
||||||
|
|
||||||
|
if result then begin
|
||||||
|
WatcherThreadHandle := Windows.CreateThread(nil, 0, @WatcherThreadProc, nil, 0, WatcherThreadId);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Leave;
|
||||||
|
end;
|
||||||
|
end; // .function RunWatcher
|
||||||
|
|
||||||
|
var
|
||||||
|
DirChange: TDirChange;
|
||||||
|
|
||||||
|
begin
|
||||||
|
WatcherCritSection.Init;
|
||||||
|
RunWatcher(GetCurrentDir + '\Tests\', 250);
|
||||||
|
|
||||||
|
// with ReadDirChanges('D:') do begin
|
||||||
|
// while IterNext(DirChange, 0) do begin
|
||||||
|
// VarDump([ord(DirChange.Action), DirChange.FilePath]);
|
||||||
|
// end;
|
||||||
|
// end;
|
||||||
|
|
||||||
|
// exit;
|
||||||
|
end.
|
||||||
Loading…
Reference in New Issue
Block a user