how do I free memory of these .txt files - windows

Working with TXT files I've been seeing a message lately "cannot create file ('c:\01.txt') the process cannot acess the file because it is being used by another process" how do I free memory of these .txt files?
I have this code by Mr. Nice and when i trying to change or add new using: ListBox1.Items.savetofile('01.txt');
"cannot create file ('c:\01.txt') the process cannot acess the file because it is being used by another process"
var
path: string;
SR: TSearchRec;
tempFile: TextFile;
line: string;
begin
path:= 'C:\my all txt\';
if FindFirst(path + '*.txt', faAnyFile, SR) = 0 then
begin
repeat
if (SR.Attr <> faDirectory) then
begin
AssignFile(tempFile, path + SR.Name);
Reset(tempFile);
while not Eof(tempFile) do
begin
Readln(tempFile, line);
ListBox1.Items.Add(line);
end;
end;
until FindNext(SR) <> 0;
FindClose(SR);
end;
end;

You never close files after you're finished with them. You need to use CloseFile once you've reached Eof(tempfile):
while not Eof(tempfile) do
begin
Readln(tempfile, line);
ListBox1.Items.Add(line);
end;
CloseFile(tempfile);

Related

Install the same file to all subdirectories in Inno Setup

I am newbie with Inno Setup and I have been reading some threads but could not find how to do the following.
I simply would like to search for folders within a directory and in each folder detected install the same file with no selection of wizard page shown to the user. Not recursive, only files inside the detected folders and not subfolders.
I meant to install the same file in all folders detected while giving no option to the user to choose from. However, all other pages in the installer would be displayed as usual.
Thanks in advance
Tag the file with dontcopy flag and then install it programmatically in CurStepChanged(ssInstall) (or ssPostInstall).
Use ExtractTemporaryFile to extract the file to a temporary folder.
Use FindFirst/FindNext functions to find the subfolders.
Use FileCopy to copy the file from a temporary folder to the found subfolder(s).
Log a lot.
This will work well, only if the file is not huge. Otherwise the installer will unpleasantly hang. For a good user experience with huge files, more complex solution is needed.
#define TheFileName "thefile.txt"
[Files]
Source: "{#TheFileName}"; Flags: dontcopy
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
var
RootPath: string;
TempPath: string;
DestPath: string;
FindRec: TFindRec;
Count: Integer;
begin
if CurStep = ssInstall then
begin
Log('Extracting {#TheFileName}...');
ExtractTemporaryFile('{#TheFileName}');
TempPath := ExpandConstant('{tmp}\{#TheFileName}');
RootPath := ExpandConstant('{app}');
Log(Format('Searching in "%s"...', [RootPath]));
Count := 0;
if not FindFirst(RootPath + '\*', FindRec) then
begin
Log(Format('"%s" not found.', [RootPath]));
end
else
begin
try
repeat
if (FindRec.Name <> '.') and (FindRec.Name <> '..') and
(FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0) then
begin
Log(Format('Found "%s".', [FindRec.Name]));
DestPath := RootPath + '\' + FindRec.Name + '\{#TheFileName}';
if FileCopy(TempPath, DestPath, False) then
begin
Log(Format('The file was installed to "%s".', [DestPath]));
Inc(Count);
end
else
begin
Log(Format('Error installing the file to "%s".', [DestPath]));
end;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
if Count = 0 then
begin
Log(Format('No subfolder to install file "%s" to was found in "%s".', [
'{#TheFileName}', RootPath]));
end
else
begin
Log(Format('File "%s" was installed to %d subfolder(s) of "%s".', [
'{#TheFileName}', Count, RootPath]));
end;
end;
end;
end;
Alternatively, if you have a fixed set of folders, you can generate entry for each folder in the [Files] section using preprocessor:
[Files]
#define FolderEntry(Name) \
"Source: ""C:\source\*""; DestDir: ""{app}\" + Name + """; " + \
"Check: CheckDir('" + Name + "')"
#emit FolderEntry('2023')
#emit FolderEntry('2024')
#emit FolderEntry('2025')
[Code]
function CheckDir(DirName: string): Boolean;
begin
Result := DirExists(ExpandConstant('{app}') + '\' + DirName);
end;
If you add SaveToFile to the end of the script:
#expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")
... then you should see in Preprocessed.iss that the code generates a script like this:
[Files]
Source: "C:\source\*"; DestDir: "{app}\2023"; Check: CheckDir('2023')
Source: "C:\source\*"; DestDir: "{app}\2024"; Check: CheckDir('2024')
Source: "C:\source\*"; DestDir: "{app}\2025"; Check: CheckDir('2025')
Thanks a lot Martin! I did it another way that may be of help to other users.
I set a file for every potential folder I want to detect (it was only for four).
[Files] Source: "C:\Users\XXXXX\dll\*"; DestDir: "{commonappdata}\XXXX\2023"; Check:CheckDir2023;
Then I use the following to check if the folder exists:
function CheckDir2023 : Boolean;
begin
if (DirExists(ExpandConstant('{commonappdata}\xxxxxx\2023\'))) then
begin
Result := True;
end
else
begin
Result := False;
end;
end;

How to extracts files that are in folder within a ZIP file with Inno Setup?

Well, what I have is a Python project, and now am creating an installer.
I added to that the project's file .exe and a .zip file.
The zip file contains .exe modules, data, etc. And its structure is like this:
example.zip:
|---project-folder:
|----here will be the files.
|----here will be the files.
What I want is to extract those files that are inside the project-folder. So the .exe can run.
I have this code to extract a zip file:
[Code]
procedure InitializeWizard;
begin
ForceDirectories(ExpandConstant('{localappdata}\folder-A\app\folder-B'))
end;
const
SHCONTCH_NOPROGRESSBOX = 4;
SHCONTCH_RESPONDYESTOALL = 16;
procedure unzip(ZipFile, TargetFldr: variant);
var
shellobj: variant;
SrcFldr, DestFldr: variant;
shellfldritems: variant;
begin
if FileExists(ZipFile) then begin
if not DirExists(TargetFldr) then
if not ForceDirectories(TargetFldr) then begin
MsgBox('Can not create folder '+TargetFldr+' !!', mbError, MB_OK);
Exit;
end;
shellobj := CreateOleObject('Shell.Application');
SrcFldr := shellobj.NameSpace(ZipFile);
DestFldr := shellobj.NameSpace(TargetFldr);
shellfldritems := SrcFldr.Items;
DestFldr.CopyHere(
shellfldritems, SHCONTCH_NOPROGRESSBOX or SHCONTCH_RESPONDYESTOALL);
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
unzip(ExpandConstant('{app}\example.zip'),ExpandConstant('{app}\'));
end;
end;
The results:
app.exe
unins.bat
unins.exe
example.zip (and I want this zip file to be deleted after extracting)
project-folder (here I want the files inside the folders)
What I want:
app.exe
unins.bat
unins.exe
files... (files that are inside the project-folder)
Refer directly to the subfolder:
SrcFldr := shellobj.NameSpace(ZipFile + '\project-folder');

Error: "Cannot open file "20210609.log". The process cannot access the file because it is being used by another process"

I've got some third party code that writes to a log file one line at a time using the code below:
procedure TLog.WriteToLog(Entry: ansistring);
var
strFile: string;
fStream: TFileStream;
strDT: ansistring;
begin
if ((strLogDirectory<>'') and (strFileRoot<>'')) then
begin
if not(DirectoryExists(strLogDirectory)) then
ForceDirectories(strLogDirectory);
strFile:=strLogDirectory + '\' + strFileRoot + '-' + strFilename;
if FileExists(strFile) then
fStream:=TFileStream.Create(strFile, fmOpenReadWrite)
else
fStream:=TFileStream.Create(strFile, fmCreate);
fStream.Seek(0, soEnd);
if blnUseTimeStamp then
strDT:=formatdatetime(strDateFmt + ' hh:mm:ss', Now) + ' ' + Entry + chr(13) + chr(10)
else
strDT:=Entry + chr(13) + chr(10);
fStream.WriteBuffer(strDT[1], length(strDT));
FreeandNil(fStream);
end;
end;
This has previously been working fine at a client site but in the last few weeks it is now getting the error in the title.
There is no other process that should have the file open. I suspect it is Anti-Virus but the client claims they have disabled the AntiV and they still get the error.
The error ONLY seems to occur when the code is in a loop and writing lines fast.
WHAT I WANT TO KNOW:
Assuming it is not the Anti-Virus (or similar) causing the problem, could it be due to the operating system not clearing a flag (or something similar) before the next time it tries to write to the file?
WHAT I WANT TO KNOW: Assuming it is not the Anti-Virus (or similar) causing the problem, could it be due to the operating system not clearing a flag (or something similar) before the next time it tries to write to the file?
No, this is not a speed issue, or a caching issue. It is a sharing violation, which means there MUST be another open handle to the same file, where that handle has sharing rights assigned (or lack of) which are incompatible with the rights being requested by this code.
For example, if that other handle is not sharing read+write access, then this code will fail to open the file when creating the TFileStream with fmOpenReadWrite. If any handle is open to the file, this code will fail when creating the TFileStream with fmCreate, as that requests Exclusive access to the file by default.
I would suggest something more like this instead:
procedure TLog.WriteToLog(Entry: AnsiString);
var
strFile: string;
fStream: TFileStream;
strDT: AnsiString;
fMode: Word;
begin
if (strLogDirectory <> '') and (strFileRoot <> '') then
begin
ForceDirectories(strLogDirectory);
strFile := IncludeTrailingPathDelimiter(strLogDirectory) + strFileRoot + '-' + strFilename;
fMode := fmOpenReadWrite or fmShareDenyWrite;
if not FileExists(strFile) then fMode := fMode or fmCreate;
fStream := TFileStream.Create(strFile, fMode);
try
fStream.Seek(0, soEnd);
if blnUseTimeStamp then
strDT := FormatDateTime(strDateFmt + ' hh:mm:ss', Now) + ' ' + Entry + sLineBreak
else
strDT := Entry + sLineBreak;
fStream.WriteBuffer(strDT[1], Length(strDT));
finally
fStream.Free;
end;
end;
end;
However, do note that using FileExists() introduces a TOCTOU race condition. The file might be deleted/created by someone else after the existence is checked and before the file is opened/created. Best to let the OS handle this for you.
At least on Windows, you can use CreateFile() directly with the OPEN_ALWAYS flag (TFileStream only ever uses CREATE_ALWAYS, CREATE_NEW, or OPEN_EXISTING), and then assign the resulting THandle to a THandleStream, eg:
procedure TLog.WriteToLog(Entry: AnsiString);
var
strFile: string;
hFile: THandle;
fStream: THandleStream;
strDT: AnsiString;
begin
if (strLogDirectory <> '') and (strFileRoot <> '') then
begin
ForceDirectories(strLogDirectory);
strFile := IncludeTrailingPathDelimiter(strLogDirectory) + strFileRoot + '-' + strFilename;
hFile := CreateFile(PChar(strFile), GENERIC_WRITE, FILE_SHARE_READ, nil, OPEN_ALWAYS, 0, 0);
if hFile = INVALID_HANDLE_VALUE then RaiseLastOSError;
try
fStream := THandleStream.Create(hFile);
try
fStream.Seek(0, soEnd);
if blnUseTimeStamp then
strDT := FormatDateTime(strDateFmt + ' hh:mm:ss', Now) + ' ' + Entry + sLineBreak
else
strDT := Entry + sLineBreak;
fStream.WriteBuffer(strDT[1], Length(strDT));
finally
fStream.Free;
end;
finally
CloseHandle(hFile);
end;
end;
end;
In any case, you can use a tool like SysInternals Process Explorer to verify if there is another handle open to the file, and which process it belongs to. If the offending handle in question is being closed before you can see it in PE, then use a tool like SysInternals Process Monitor to log access to the file in real-time and check for overlapping attempts to open the file.

Inno Setup - Copy Files with Progress Bar on a custom page

I'm currently working on a program that updates our companys software.
I'm letting a User chose the location of the installed program and a backup location in an "CreateInputDirPage"
Currently I'm creating a mask for the selection of the two directorys:
SelectPathPage := CreateInputDirPage(PreviousPageId,
'Text 1',
'Text 2.',
'Text 3', False, 'New Folder');
SelectPathPage.Add('Path to company program');
SelectPathPage.Add('Path to backup folder');
Then I'm validating with existing files if the first Folder indead holds our companys program.
Now I want to copy the first selection to a new subfolder in the Backup-Folder.
I found this sample code from another question for copying the files:
DirectoryCopy(SelectPathPage.Values[0], SelectPathPage.Values[1]);
Which seems to work with the "NextButtonClick"-Function.
How can I copy the folder and the content of the folder on a seperate mask after the "SelectPathPage"-Mask with a progress bar and making the next button available when the copy is finished.
It should be similar to the "Install"-Mask with the progress bar.
Is it even possible to create something like this in a custom mask in Inno Setup?
Thanks in Advance
Use CreateOutputProgressPage to create the progress page.
And modify the DirectoryCopy function from Copying hidden files in Inno Setup to advance the progress on the page.
To calculate the total size (to set the maximum of the progress bar), the code needs GetDirSize function from Inno Setup get directory size including subdirectories.
[Code]
const
ProgressRatio = 1024;
procedure DirectoryCopyWithProgress(
SourcePath, DestPath: string; ProgressPage: TOutputProgressWizardPage);
var
FindRec: TFindRec;
SourceFilePath: string;
DestFilePath: string;
Size: Int64;
begin
if FindFirst(SourcePath + '\*', FindRec) then
begin
try
repeat
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then
begin
SourceFilePath := SourcePath + '\' + FindRec.Name;
DestFilePath := DestPath + '\' + FindRec.Name;
ProgressPage.SetText(SourceFilePath, DestFilePath);
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then
begin
Size := Int64(FindRec.SizeHigh) shl 32 + FindRec.SizeLow;
if FileCopy(SourceFilePath, DestFilePath, False) then
begin
Log(Format('Copied %s to %s with %s bytes', [
SourceFilePath, DestFilePath, IntToStr(Size)]));
end
else
begin
Log(Format('Failed to copy %s to %s', [
SourceFilePath, DestFilePath]));
end;
end
else
begin
Size := 0;
if DirExists(DestFilePath) or CreateDir(DestFilePath) then
begin
Log(Format('Created %s', [DestFilePath]));
DirectoryCopyWithProgress(
SourceFilePath, DestFilePath, ProgressPage);
end
else
begin
Log(Format('Failed to create %s', [DestFilePath]));
end;
end;
Size := Size / ProgressRatio;
ProgressPage.SetProgress(
ProgressPage.ProgressBar.Position + Longint(Size),
ProgressPage.ProgressBar.Max);
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end
else
begin
Log(Format('Failed to list %s', [SourcePath]));
end;
end;
function SelectPathPageNextButtonClick(Sender: TWizardPage): Boolean;
var
SourcePath: string;
DestPath: string;
ProgressPage: TOutputProgressWizardPage;
TotalSize: Longint;
begin
ProgressPage := CreateOutputProgressPage('Copying files...', '');
SourcePath := TInputDirWizardPage(Sender).Values[0];
DestPath := TInputDirWizardPage(Sender).Values[1];
TotalSize := GetDirSize(SourcePath) / ProgressRatio;
Log(Format('Total size is %s', [IntToStr(TotalSize)]));
ProgressPage.SetProgress(0, TotalSize);
ProgressPage.Show;
try
DirectoryCopyWithProgress(SourcePath, DestPath, ProgressPage);
finally
ProgressPage.Hide;
ProgressPage.Free;
end;
Result := True;
end;
procedure InitializeWizard();
var
SelectPathPage: TInputDirWizardPage;
begin
SelectPathPage :=
CreateInputDirPage(
wpSelectDir, 'Text 1', 'Text 2.', 'Text 3', False, 'New Folder');
SelectPathPage.Add('Path to company program');
SelectPathPage.Add('Path to backup folder');
SelectPathPage.OnNextButtonClick := #SelectPathPageNextButtonClick;
end;

Runtime error 104 Pascal

Im very new to using files and im really struggling to fix this any help would be great.
It seems that the error is coming from my read array function but not entirely
sure i am also not to sure what the 104 error really means
thanks in advance
program ReadFromFile;
type
lineArray = array [0..19] of String;
procedure PrintArray(lines: lineArray);
var
i: Integer;
begin
for i:=0 to High(lines) do
begin
WriteLn('Text is: ', lines[i], ' Line number is: ', i);
end;
end;
function ReadArray(var myFile: TextFile):lineArray;
var
count : Integer;
lines : lineArray;
i: Integer;
begin
ReadLn(myFile, count);
for i := 0 to count do
begin
ReadLn(myFile, lines[i]);
end;
result := lines;
end;
procedure Main();
var
myFile: TextFile;
line: lineArray;
begin
AssignFile(myFile, 'mytestfile.dat');
ReWrite(myFile);
line:=ReadArray(myFile);
Close(myFile);
AssignFile(myFile, 'mytestfile.dat');
Reset(myFile);
PrintArray(line);
Close(myFile);
end;
begin
Main();
end.
You don't know what that error means. Neither do I off the top of my head. So, let's look it up in the documentation and find out. Websearch takes us here: https://www.freepascal.org/docs-html/user/userap4.html
File not open for input
Reported by Read, BlockRead, Eof, Eoln, SeekEof or SeekEoln if the file is not opened with Reset.
You have your calls to open the file the wrong way round. Call Reset to open for reading, Rewrite to open for writing.
Notes:
Looping from 0 to count will perform count + 1 iterations. I'd expect to see you looping from 0 to count - 1.
You don't check whether your array is long enough. You therefore run the risk of a buffer overrun. A dynamic array would avoid this.
It's not clear why you open the file for a second time when you print the contents to the console.
You could have looked up the error code yourself. Please take the hint to do web search the next time you encounter an error like this.

Resources