Delphi 2010 - Is File in Use? - delphi

I have a function which works ok to check if a local file is in use.
However if I map a network drive and try to check if a file from the mapped drive is in use then the result of the function is always false.
I need to wait before a large file is being copied to the mapped drive and after completion I rename the file.
If the file in not in use then i start performing various actions else i wait another minute and check again.
How can I modify the function below in order to work with mapped drive files that are constantly copied?
Thank you
function IsFileInUse(FileName: TFileName): Boolean;
var
HFileRes: HFILE;
begin
Result := False;
if not FileExists(FileName) then
begin
showmessage('Fisierul "'+Filename+'" nu exista!');
Exit;
end
else
begin
HFileRes := CreateFile(PChar(FileName),
GENERIC_READ or GENERIC_WRITE,
0,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
Result := (HFileRes = INVALID_HANDLE_VALUE);
if not Result then
CloseHandle(HFileRes);
end;
end;

What you are claiming is that CreateFile succeeds in opening a file in exclusive mode whilst another party is writing to the file. Possible explanations:
You have made a mistake with the file name and the file you are opening is not the one in use.
The other party is writing to the file without having locked it. In other words it opened the file with a share mode that allowed other parties to read and write. This would be quite unusual. If this is the case then you need to fix the other process.
The remote file server is broken and fails to respect locks. I'd regard this as quite unlikely.
I think the final option can be rejected immediately. Option 2 seems rather unlikely. Which leaves option 1. You are able to lock the file because it is not locked.
I'd also comment that the function is spurious. You can remove it. Simply attempt whatever operation you need to perform. If that operation fails due to a sharing violation you know that the file was locked. Consider also the race condition in any code using that function. The fact that a file is unlocked now does not prevent another party locking the file before you can do anything with it.

Related

FileGetDate works some times, thoughts?

Some files this works with and others it does not.
var
Src : integer;
FileDate : LongInt;
begin
Src:=FileOpen(SrcPath,fmOpenRead);
FileDate:=FileGetDate(Src); // Crash here with FileDate = -1
...
FileSetDate(Dest,FileDate);
I have checked Attributes for files that work and some that do not and they are identical.
Same for "Security," identical.
"Src" is a valid Integer for the ones that work and the ones that do not work.
The only thing I can see is that the full path to the ones that do not can be 130 characters and longer. But I renamed some Folders and shortened that to 118 and still no good.
Got me baffled. In a 2000+ file copy process, just 149 all in the same sub-Folder crash at this FileGetDate.
Any suggestions?
Thanks
The call to FileGetDate returns -1. The documentation says this:
The return value is -1 if the handle is invalid.
In other words, the handle returned by your call to FileOpen is not valid. You don't check for any errors in the code. Your code makes the assumption that all the calls succeed. The failure mode for FileOpen is that it returns -1. You are not checking the return value of FileOpen. You must add code to do so.
Note that the documentation for FileOpen says:
Note: We do not encourage the use of the non-native Delphi language file handlers such as FileOpen. These routines map to system
routines and return OS file handles, not normal Delphi file variables.
These are low-level file access routines. For normal file operations
use AssignFile, Rewrite, and Reset instead.
So even ancient legacy Pascal I/O is to be preferred to FileOpen.
Frankly, if you want to work with files and get meaningful error diagnostics, you should use the Win32 API. Call CreateFile and if it fails, check GetLastError to find out why. There are lots of ways in which a file open request can fail and realistically only you can work out what the reason is for your files. We don't have the files at hand, only you do.
Finally, you say that you are writing a file copy routine. The system already provides such a thing, and you would be far better off using it. You are spending a lot of effort re-inventing the wheel. What's more, writing a good file copy function is hard. The one that the system provides is known to work. Your version is liable to be inferior.
To copy a single file you can use CopyFile or CopyGFileEx. But you are copying multiple files and SHFileOperation is the API for that.
3 thoughts,
The first is that something else has exclusive access to the file and you simply can not open it regardless. Check then your opened file handle is valid.
The second though is that some files can have VERY damaged time stamps on them. I am not sure how it happens, I just know that it does.
Finally, according to the documents on Linux, -1 is a valid date value, you do not mention what file system your source files are stored on.
Here is the implementation of FileGetDate() in Delphi 5:
function FileGetDate(Handle: Integer): Integer;
var
FileTime, LocalFileTime: TFileTime;
begin
if GetFileTime(THandle(Handle), nil, nil, #FileTime) and
FileTimeToLocalFileTime(FileTime, LocalFileTime) and
FileTimeToDosDateTime(LocalFileTime, LongRec(Result).Hi,
LongRec(Result).Lo) then Exit;
Result := -1;
end;
That is 3 different points of failure that could happen on any given input file handle:
does GetFileTime() fail?
does FileTimeToLocalFileTime() fail?
does FileTimeToDosDateTime() fail?
Unless FileOpen() fails (which you are not checking for - it returns -1 if it is not able to open the file), then it is unlikely (but not impossible) that #1 or #2 are failing. But #3 does have a documented caveat:
The MS-DOS date format can represent only dates between 1/1/1980 and 12/31/2107; this conversion fails if the input file time is outside this range.
It is not likely that you encounter files with timestamps in the year 2108 and later, but you can certainly encounter files with timestamps in the year 1979 and earlier.
All 4 functions (counting the CreateFile() function called inside of FileOpen()) report an error code via GetLastError(), so you can do this:
var
Src : integer;
FileDate : LongInt;
begin
Src := FileOpen(SrcPath, fmOpenRead);
Win32Check(Src <> -1);
FileDate := FileGetDate(Src);
Win32Check(FileDate <> -1);
...
Win32Check(FileSetDate(Dest, FileDate) = 0);
Win32Check() calls RaiseLastWin32Error() if the input parameter is false. RaiseLastWin32Error() raises an EOSError exception containing the actual error code in its ErrorCode property.
If FileGetDate() fails, obviously you won't know which Win32 function actually failed. That is where the debugger comes into play. Enable Debug DCUs in your Project Options to allow you to step into the VCL/RTL source code. Find a file that fails, call FileGetDate() on it, and step through its source code to see which if the three API functions is actually failing.
Similarly for FileSetDate(), which also calls 3 API functions internally:
function FileSetDate(Handle: Integer; Age: Integer): Integer;
var
LocalFileTime, FileTime: TFileTime;
begin
Result := 0;
if DosDateTimeToFileTime(LongRec(Age).Hi, LongRec(Age).Lo, LocalFileTime) and
LocalFileTimeToFileTime(LocalFileTime, FileTime) and
SetFileTime(Handle, nil, nil, #FileTime) then Exit;
Result := GetLastError;
end;
If FileSetDate() fails, is it because:
DosDateTimeToFileTime() failed?
LocalFileTimeToFileTime() failed?
does SetFileTime() failed?

Checking if the file is in use and by which application?

Trying to use the below mentioned approach to get more details about the locked file.
Is file in use
function GetFileInUseInfo(const FileName : WideString) : IFileIsInUse;
var
ROT : IRunningObjectTable;
mFile, enumIndex, Prefix : IMoniker;
enumMoniker : IEnumMoniker;
MonikerType : LongInt;
unkInt : IInterface;
begin
result := nil;
OleCheck(GetRunningObjectTable(0, ROT));
OleCheck(CreateFileMoniker(PWideChar(FileName), mFile));
OleCheck(ROT.EnumRunning(enumMoniker));
while (enumMoniker.Next(1, enumIndex, nil) = S_OK) do
begin
OleCheck(enumIndex.IsSystemMoniker(MonikerType));
if MonikerType = MKSYS_FILEMONIKER then
begin
if Succeeded(mFile.CommonPrefixWith(enumIndex, Prefix)) and
(mFile.IsEqual(Prefix) = S_OK) then
begin
if Succeeded(ROT.GetObject(enumIndex, unkInt)) then
begin
if Succeeded(unkInt.QueryInterface(IID_IFileIsInUse, result)) then
begin
result := unkInt as IFileIsInUse;
exit;
end;
end;
end;
end;
end;
end;
But the call to
unkInt.QueryInterface(IID_IFileIsInUse, result)
always returns E_NOINTERFACE.
Platform: Windows 7 32 bit-OS, opening word files and .msg files.
Checked opening files from the explorer and trying to delete. It shows proper details about the application in which the file is opened. In my application, I am try to display the information about application in which the file is opened. But when trying to cast the pointer to IFileIsInUse interface, QueryInterface calls fails with return code E_NOINTERFACE which means the object in ROT does not implement IFileIsInUse. AFASIK, MS Office files implements IFileIsInUse
Any idea what is wrong here?
In fact your code works fine. The problem is that the programs you are testing against really do not implement IFileIsInUse. When the system returns E_NOINTERFACE it is accurate. The interface is not implemented.
I tested this with the File Is In Use Sample from the SDK. Files that are added to the ROT by that application, which does implement IFileIsInUse, were picked up by your code. On the other hand, files opened by Acrobat 8 and Word 2010 were not.
The conclusion that I draw from this is that IFileIsInUse is a fine idea in principle, but not much use if applications don't support it. And it appears that there are major applications that do not.
It is clear that you will need to use one or more of the other mechanisms to detect which application has a file locked when you find that IFileIsInUse is not implemented.
SysInternals Process Explorer worked for me to delete a locked .msg file that was causing system problems like locking up the desktop.
Run Process Explorer, use the Find menu,
enter the full path file name,
hit Search.
For deleting a locked file, I opened a cmd window and tried to del the locked file, but the delete hung on the lock.
Then I used Process Explorer to restart the process holding the lock - Explorer.exe.
The del then completed successfully.

Is there a way to get my delphi program to wait for a large number of files to be copied?

I have written a program that does the following...
Monitors a folder for the creation of a new file with a specific filename that will eventually be created in a sub folder.
On creation of the file, the sub folders path is added to a queue in the form of a TList.
The files must be processed in the creation order.
A procedure is called to process all the files (images in this case) in the subfolder which involves moving the files to a network location.
The subfolder path is removed from the queue (TList).
If any more paths exist in the queue, the next path is passed to the processing procedure.
The problem I am having is that the time to copy the files to a network location varies depending on the number and size of the images so...
Is there a way to get Delphi to wait for procedure of file operation to finish?
I tried a while loop that waited for a boolean value to change (changed when the last file to be copied was found on the network) but that hung the application (even with application.processMessage) and the dirMonitor component failed to add the next sub folder to the TList.
Any suggestions would be most appreciated.
Thanks in advance.
Thanks for the replys...
I had a look at OmniThread which looks ideal... although I only have access to Delphi 7 so its a no go.
The problem Im having is that the folders take varying amounts of time to transfer due to differing sizes and network traffic etc... When a folder with a lot of images is followed by a folder with only a few images, the smaller of the two is reaching the network destination first. The network desination being a third party print spooler so the prints come off in the wrong order.
The simplified code:
procedure TForm1.programTimerTimer(Sender: TObject);
begin
if (fileOperationInProgress = false) AND (programPaused = false) then
begin
processOrderQueue;
end;
end;
procedure TForm1.processOrderQueue;
begin
// gets folder paths from queue
// processes images
// copy to print spooler (network location)
copyFolder(fromPath, toPath);
// remove temp files
end;
procedure TForm1.copyFolder(copyFrom : String; copyTo : String);
var
fos : TSHFileOpStruct;
begin
fileOperationInProgress := True;
ZeroMemory(#fos, SizeOf(fos));
with fos do
begin
wFunc := FO_COPY;
fFlags := FOF_FILESONLY or FOF_SILENT;
pFrom := PChar(copyFrom);
pTo := PChar(copyTo)
end;
ShFileOperation(fos);
fileOperationInProgress := False;
end;
Think I've come up with the answer... I'm going to do all file operationions in a single thread and set a global 'busy' boolean when it starts and change it again on completion.
That way the shell monitor won't miss messages when any file operations are in progress.
You could implement a file system watch. Essentially, you create a file handle with the following flags:
CreateFile(PChar(FDirectoryToWatch), FILE_LIST_DIRECTORY or GENERIC_READ,
FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0);
and then create a loop to call ReadDirectoryChangesW.
Linked is an example class:
Why does ReadDirectoryChangesW omit events?
The only thing I would do differently is provide an event in the creation of the class to notify of changes (remembering that when calling the event in the Execute procedure it probably needs to be Synchronized).

Delphi Determine filesize in real time

Is it possible in Delphi to determine the size of a file as it is being copied? I get a notification when a file is first copied to a folder, but need to wait until the copy is complete before I can process the file.
I've used JclFileUtils.GetSizeOfFile(Filename) but that gives me the 'expected' file size, not the current filesize.
Regards, Pieter
Prompted by the first answer I decided to give up on trying to determine when a file copy has completed. Instead I found that using TFileStream gave me a reliable indication whether a file is in use or not.
function IsFileInUse(Filename: string; var ResultMessage: string): boolean;
var
Stream: TFileStream;
begin
Result := True;
ResultMessage := '';
try
Stream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite);
try
Result := False;
finally
FreeAndNil(Stream);
end;
Except on E: Exception do
ResultMessage := 'IsFileInUse: ' + E.Message
end;
end;
In this way I can keep on checking until the file is not in use anymore before attempting to process it.
It depends on the technique that is used by the copying function. Most copy-methods will allocate the disk space first before they start to copy a file. Thus, if you want to copy a file of 4 GB, the system starts by creating a file with random data for 4 GB in total. (Which is done lightning-fast, btw.) It then copies the data itself, but the file size is already what you expect.
This has as advantage that the sysmen can check if there's enough disk space available to actually copy the data.
If you write your own file copy function then you can have total control over how it does this. Else, you're limited to whatever the chosen copy-method offers you. So, how do you copy a file?
If you have control over the file copy process, it is easiest to have the copy routine create the file using a temporary filename, and when done, rename it to correct filename.
That way, you can use Windows folder monitoring to watch for the renaming (JCL contains a component to help with this, not sure about the name from here). When your code gets triggered you are sure the other side has finished writing the file.
A simple trick I used was to have the copying process create new files with a '$$$' extension. My code still got triggered for those but I ignored them until they were renamed to their proper filename.
Hope this helps.

programmatically check computer management - shared folders - open files for a file

We have a client server software that needs to be updated. I need to check if the file is currently being accessed. Is this possible if so how Delphi code if possible. The only place I can see if the file is open is under the shared folders open files. I have tried this code but just shows that the file is not opened.
function TfrmMain.FileInUse(FileName: string): Boolean;
var H_File : HFILE;
begin
Result := False;
if not FileExists(FileName) then
begin
showmessage ('Doesnt Exist');
exit;
end;
H_File := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0,
nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
Result := (H_File = INVALID_HANDLE_VALUE);
showmessage('Opened');
if not Result then
CloseHandle(H_File);
end;
There is a great deal of information you can access over the WBEM sub-system provided by Windows. I believe there are good WBEM components out there, but you could also import the "Microsoft WMI Scripting" COM Type Library (though this takes a little work to figure out how it works).
If you query for Win32_ServerConnection objects, you get a list of items currently in use, much like you can view using the 'Computer Management' tool from the Administrative Tools.
Not necessarily an answer, but I am currently doing something similar - because the main executable might be updated during working hours though I have created a intermediary application that checks to see if a locally cached copy of the file is up to date, I then run this locally cached copy.
I found this similar item, someone proposes to use the NetFileEnum function

Resources