Using Inno Setup scripting to insert lines into a text file? - text-files

Objective is to insert multiple lines (once) at beginning of text document.
But I have been having problems with the approaches I found. I've attempted to adjust them but it incorporates side-effect issues.
Two problems:
Appends end of file instead of inserting into line locations.
In its present design, it appends the file 3 times.
In reference to the other scripts I found both lines incorporating Result := resulted in unknown identifier.
References:
How to use Inno Setup scripting to append to a text file?
https://www.codeproject.com/Questions/477984/createplusandpluswriteplusbatchplusfileplusinplusi
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
var
ErrorCode: Integer;
FileName: string;
lines : TArrayOfString;
begin
fileName := ExpandConstant('{userappdata}\xy');
fileName := AddBackslash(FileName) + 'zlt.net';
SetArrayLength(lines, 6);
lines[0] := 'A';
lines[1] := 'B';
lines[2] := 'C';
lines[3] := 'D';
lines[4] := 'E';
lines[5] := 'F';
SaveStringsToFile(filename,lines,true);
end;

There's no way to insert lines into file. You have to recreate whole file.
CurStepChanged is triggered for every step of the installation. You will want to insert the lines in one of the steps only (likely ssInstall or ssPostInstall)
procedure CurStepChanged(CurStep: TSetupStep);
var
Lines: TStringList;
begin
if CurStep = ssPostInstall then
begin
Lines := TStringList.Create;
try
Lines.LoadFromFile(FileName);
Lines.Insert(0, 'A');
Lines.Insert(1, 'B');
Lines.Insert(2, 'C');
Lines.Insert(3, 'D');
Lines.Insert(4, 'E');
Lines.Insert(5, 'F');
Lines.SaveToFile(FileName);
finally
Lines.Free;
end;
end;
end;
The code loads whole file to memory. That can be a problem if the file is too large. But that should not be a problem for files smaller than tens of MBs. Also repeatedly calling Insert is not efficient. But for small files and few lines, this should not be a problem either.

Related

Writing datas to memobox from .txt file using Lazarus freepascal?

I've got a project at schools which requires to write datas from a .txt file to a "memobox" in Lazarus freepascal.
There are datas in order like this.
Budapest tomato 23
Dublin tv 45
Rosslare projector 43
etc.
I have to read these datas from a .txt file and then write them into a memobox in Lazarus freepascal.
If I am not mistaken I have already copyed the datas from the .txt file but I have no idea how to write them.
I've already written this code:
type
cityname:integer;
product:string;
quantity:integer;
var
Form1: TForm1;
ceg:array[1..5] of rektip;
db:integer;
implementation
procedure TForm1.Button1Click(Sender: TObject);
var f:textfile; i:integer; a:char;
begin
assignfile(f,'termek.txt');
reset(f);
readln(f,db);
While not eof(f) do
begin
readln(f,ceg[i].varosnev,a,ceg[i].termek,a,ceg[i].darabszam);
end;
db:=1;
closefile(f);
end;
procedure TForm1.Button2Click(Sender: TObject);
var i:integer;
begin
For i:=1 to db do
Memo1.lines.add(ceg[i].varosnev,ceg[i].termek,IntToStr(ceg[i].darabszam));
end;
end;
I would like to know how to fix it.
The code you posted is incomplete, so I assume:
type
rektip = record
varosnev: string;
termek: string;
darabszam: Integer;
end;
There is a lot wrong with your approach:
readln(f,db);
While not eof(f) do
begin
readln(f,ceg[i].varosnev,a,ceg[i].termek,a,ceg[i].darabszam);
end;
db:=1;
closefile(f);
end;
You are not initializing nor updating i, so you are reading all data into the same record, and you don't even know which one (i could be 100 and you'd be writing the data to somewhere unknown -- there is no ceg[100]). That results in undefined behaviour (and that can be a crash too).
Do something like:
var
ceg: array of rektip;
...
begin
AssignFile(f, 'termek.txt');
Reset(f);
Readln(f, db);
if db = 0 then
Exit;
SetLength(ceg, db);
i := 0;
while not eof(f) do
begin
Readln(f, ceg[i].varosnev, ceg[i].termek, ceg[i].darabszam);
Inc(i);
if i > High(ceg) then
Break;
end;
SetLength(ceg, i); // remove any empty slots.
CloseFile(f);
end;
Now you can put them into the TMemo:
for i := Low(ceg) to High(ceg) do
begin
Memo1.Lines.Add(Format('%s %s %d', [ceg[i].varosnev, ceg[i].termek, ceg[i].darabszam]));
end;
Note that the code above, reading from the file, assumes the file looks like:
3
Budapest tomato 23
Dublin tv 45
Rosslare projector 43
i.e. each "record" is on a line of its own and the first line contains the number of records.

Pascal: Only displaying one set of array's and not the rest from file

So my code only reads one set of albums from my textfile and refused to read the rest in the file. For some reason why I get it to read album, it only displays that the file contain "1" album instead of "2".Even though in my loop i've SetLength the array to equal the albumNumber so the array size should be set to that when i begin my for loop.
textfile
2 (how many albums)
Adele (name)
Pop (genre)
2 (tracks)
Hello (Track 1)
ff (location)
Remedy (track 2)
dd (location)
Jcole
Rap
2
Toto
ff
Africa
dd
pascal
type
TrackRec = record
name: String;
location: String;
end;
GenreType = (Pop, Rap, Rock, Classic);
AlbumRec = Record
name: String;
genre: GenreType;
tracks: array of TrackRec;
end;
type AlbumArray = array of AlbumRec;
procedure ReadAlbum(var albums: AlbumArray; var myFile: TextFile);
var
albumNumber, tracknumber, count, i: Integer;
begin
AssignFile(myFile, 'mytestfile.dat');
Reset(myFile);
ReadLn(myFile, albumNumber);
WriteLn('This file contained ', albumNumber, ' album/s');
SetLength(albums, albumNumber);
for i := 0 to High(albums) do
begin
ReadLn(myFile, albums[i].name);
ReadLn(myFile, albums[i].genre);
ReadLn(myFile, tracknumber);
SetLength(albums[i].tracks, tracknumber);
for count := Low(albums[count].tracks) to tracknumber - 1 do
begin
ReadLn(myFile, albums[i].tracks[count].name);
ReadLn(myFile, albums[i].tracks[count].location);
end;
end;
end;
procedure Main();
var
i, count, select, change: Integer;
albums: AlbumArray;
myFile: TextFile;
message: String;
begin
WriteLn('Please select an option: ');
WriteLn('-------------------------');
WriteLn('1. Read Filename');
WriteLn('2. Display Albums');
WriteLn('3. Select an Album');
WriteLn('4. Update an Album');
WriteLn('5. Exit');
WriteLn('-------------------------');
repeat
i := ReadInteger('Select option for menu:');
case i of
1: ReadAlbum(albums, myFile);
2: PrintAll(albums, myFile);
3: PlayAlbum(albums, myFile);
4: Update(albums, myFile);
end;
until i = 5;
end;
Your code includes a number of routines (e.g. PlayAlbum) for which you
have not included the source.
Anyway, you may be relieved to know that in fact your ReadAlbum procedure does
work correctly, but perhaps you have gotten into a muddle thinking that it does not.
Down below, I've explained how you could have debugged it
to verify that it works. For now, replace your ReadAlbum and Main by the code below,
which also includes a couple of procedures to display the results.
Once you've done that, compile and run the app.
Code:
procedure ReadAlbum(var albums: AlbumArray; var myFile: TextFile);
var
albumNumber, tracknumber, count, i: Integer;
begin
AssignFile(myFile, 'D:\Delphi\Code\Lazarus\mytestfile.dat');
Reset(myFile);
ReadLn(myFile, albumNumber);
WriteLn('This file contained ', albumNumber, ' album/s');
WriteLn; // To space out the display
SetLength(albums, albumNumber);
for i := 0 to High(albums) do
begin
ReadLn(myFile, albums[i].name);
ReadLn(myFile, albums[i].genre);
ReadLn(myFile, tracknumber);
SetLength(albums[i].tracks, tracknumber);
for count := Low(albums[count].tracks) to tracknumber - 1 do
begin
ReadLn(myFile, albums[i].tracks[count].name);
ReadLn(myFile, albums[i].tracks[count].location);
end;
end;
end;
procedure PrintAlbum(Albums : AlbumArray; AlbumNumber : Integer);
var
Tracks : Integer;
Album : AlbumRec;
begin
// Note : This is incomplete, you should complete it yourself!
// I've used the local variable Album to avoid having to keep typing
// Albums[AlbumNumber] and because it makes inspection during debugging easier
Album := Albums[AlbumNumber];
WriteLn('Album number: ', AlbumNumber);
Writeln('AlbumName: ', Album.Name);
Writeln('AlbumGenre: ', Album.Genre);
Writeln; // to Space out the display;
end;
procedure PrintAlbums(Albums : AlbumArray);
var
AlbumNumber : Integer;
Album : AlbumRec;
begin
for AlbumNumber := 0 to High(Albums) do begin
Album := Albums[AlbumNumber];
PrintAlbum(Albums, AlbumNumber);
end;
end;
procedure Main();
var
albums: AlbumArray;
myFile: TextFile;
begin
ReadAlbum(Albums, MyFile);
PrintAlbums(Albums);
WriteLn('Done');
ReadLn;
end;
begin
Main;
end.
The following explains how to start debugging in the freeware Lazarus
IDE for FPC.
1 With your project open in the IDE, set a debugger breakpoint on the line
for i := 0 to High(albums) do
by either pressing F5 or clicking the blue circle in the "gutter"
on the left of the editor window. Either way, the line should turn red.
2 Compile and run the application. The black console window should appear
and then the debugger will stop on the BP set in step 1.
3 Press Ctrl-F5. This will pop op a Watch Properties dialog.
A watch is a way to get the debugger to display the value of a variable
as the program executes. Enter
Albums
in the Expression box.
If necessary, drag the Watch List window so that it doesn't overlap the code
editor window or the console window.
4 Now, repeatedly press F8 and observe the Watch List window carefully. F8 causes the debugger to execute the program a line at a time, called "single-stepping" for
obvious reasons. Note that the Watch List window already "knows" that there are 2 Album records and what there fields are.
As you single step through the for i:= and for count :=, notice how the fields
of the two records are progressively filled out.
Btw, Count is not a good name for a variable which actually rep
resents the TrackNumber
5 Eventually, the for i:= loop will finish, and at the point you know that the Albums array is correctly set up, including the second Album record.
6 Once you're a bit more familiar with the debugger, you could delete the BP you set in step 1. Instead, place a BP on the final line of ReadAlbums. After compiling and running the application, it will stop on the BP so that you can verify Albums's content without having to single-step each line of the two for loops.
7 Now, complete the coding of PrintAlbum. If necessary you can debug it as per steps 1-6
8 Search for online Lazarus debugging tutorials and read them until it gets boring.
Hopefully, by the time you've done all this, you'll have a better idea what information (including code) readers need to have in order to help. The key to
getting good help is to enable the reader to reproduce the problem, and your q doesn't do that. Readers shouldn't have to guess what missing code is supposed to do. Even if it is obvious, the problem may actually turn out to be somewhere in the code you haven't included, in which case readers can't help anyway.

How can I use TFileStream to truncate the existing file before overwriting it?

I am using the code below to write to an existing file, but the contents get appended. What TFileStream options are necessary to empty the file and overwrite it?
procedure TUtilitiesForm.btnSaveClick(Sender: TObject);
var fs: TFileStream;
begin
fs := TFileStream.Create(FileNameEdit1.Text, fmOpenWrite);
fs.Seek(0,fsFromEnd);
mmoDDL.Lines.SaveToStream(fs);
fs.Free;
end;
Using fsFromEnd you append data beyond the end of an existing file, on the other hand fsFromBeginning starts from the beginning but won't truncate the file.
Change from fmOpenWrite to fmCreate
procedure TUtilitiesForm.btnSaveClick(Sender: TObject);
var fs: TFileStream;
begin
fs := TFileStream.Create(FileNameEdit1.Text, fmCreate);
try
mmoDDL.Lines.SaveToStream(fs);
finally
FreeAndNil(fs);
end;
end;

Extracting BLOB files from Oracle table with UTL_FILE en masse, some compressed some not

I have a script that extracts documents en masse out of an Oracle BLOB table. This is necessary for a huge rewrite and database conversion from Oracle to SQL where the files are going to be stored in an SQL file table. Since the documents have to sit on the file system, I have to get them out and write them out as files. It works great for MOST of my documents. After lots of banging my head on my desk, I finally figured out it's because there is some logic on the front end system that compresses some of the documents--although I really can't figure out the criteria for it doing that. At any rate, I've searched and searched and can't find any sort of Boolean check to see if they are compressed inside the Oracle BLOB table or not, before I extract them. If I try to decompress them ALL as I'm extracting them, I get an error on the ones that weren't compressed. So now I'm thinking I can run them all with the decompression, and then catch the exception and handle the others by exporting without the decompression. I just can't get my syntax correct in my script. This is a new challenge for me, and I don't have a ton of experience writing scripts of this sort, so please forgive my ignorance. Here's the error I get when I try to decompress all of them, so this is what I'm trying to catch:
ORA-29294: A data error occurred during compression or uncompression.
ORA-06512: at "SYS.UTL_SYS_COMPRESS", line 56
ORA-06512: at "SYS.UTL_SYS_COMPRESS", line 226
ORA-06512: at "SYS.UTL_COMPRESS", line 89
ORA-06512: at line 21
Here is the script:
DECLARE
CURSOR C1 IS Select FILE_ID || '---' || substr(DOCUMENTLOCATION,1,instr (DOCUMENTLOCATION,'.')-1)||'.doc' as FILE_NAME, FILE_BLOB, FILE_ID
From DOCUMENTS d inner join CASEJOURNAL c on d.FILE_ID = c.JOURNALENTRYID where (JOURNAL_ENTRY_TYPE = 117 or JOURNAL_ENTRY_TYPE = 3) AND c.DOCUMENTLOCATION Is Not Null AND d.MIME_TYPE = 'application/msword' AND FILE_ID between 1 and 10000;
v_blob_uncomp BLOB;
v_blob BLOB;
blob_length INTEGER;
out_file UTL_FILE.FILE_TYPE;
v_buffer RAW(32767);
chunk_size BINARY_INTEGER := 32767;
blob_position INTEGER := 1;
filename varchar2(255);
BEGIN
--Select BLOB file into variables
FOR I in C1
LOOP
filename := i.FILE_NAME;
v_blob_uncomp := UTL_COMPRESS.LZ_UNCOMPRESS(i.FILE_BLOB);
v_blob := i.FILE_BLOB;
-- Define the output directory
out_file := UTL_FILE.FOPEN('fileloc',filename,'wb',chunk_size);
--Get length of BLOB file and save to variable.
blob_length := DBMS_LOB.getlength(v_blob);
-- Write the data to the file
WHILE blob_position <= blob_length LOOP
IF blob_position + chunk_size - 1 > blob_length THEN
chunk_size := blob_length - blob_position + 1;
END IF;
DBMS_LOB.read(v_blob_uncomp, chunk_size, blob_position, v_buffer);
UTL_FILE.PUT_RAW(out_file, v_buffer, TRUE);
blob_position := blob_position + chunk_size;
END LOOP;
UTL_FILE.FCLOSE(out_file);
END LOOP;
END;
I know that the script works when I don't decompress any blobs, however the ones that were compressed don't open. It also works when I do the decompress on certain files that I know were compressed. I'm just trying to get this to work within my loop for ALL files somehow. TIA!
When you want to catch a particular Oracle error code in PL/SQL, you basically have two options:
A) catch all exceptions; in the handler, test whether the error message matches the one you are looking for; if so, handle it; if not, re-raise it. This would look something like:
BEGIN
v_blob := := UTL_COMPRESS.LZ_UNCOMPRESS(i.FILE_BLOB);
EXCEPTION
WHEN OTHERS THEN
IF sqlerrm LIKE 'ORA-29294%' THEN
v_blob := i.FILE_BLOB;
ELSE
RAISE;
END IF;
END;
B) declare an exception variable and map it to the specific error code you care about, then catch only that exception. This would look something like this:
DECLARE
compression_error EXCEPTION;
pragma exception_init ( compression_error, -29294 );
BEGIN
v_blob := UTL_COMPRESS.LZ_UNCOMPRESS(i.FILE_BLOB);
EXCEPTION
WHEN compression_error THEN
v_blob := i.FILE_BLOB;
END;
Either way, I'd suggest wrapping this in a function.
I also note that your code shown doesn't reset blob_position to 1 when it starts processing a new BLOB.
Instead of "try and fail" approach you may consider to use magic numbers:
Read few bytes from the beginning of the file, if it starts, lets say with 504B (PK), then there is a fat chance that it would be zip archive.

InnoSetup making {app} dir for install.log

I need to create an install.log of the selected components in the install destination folder ({app}) but I'm getting in issue when i run that installer that says "File does not exist C:/tmp/exe/install.log" I'm assuming that means it has not created the dir "exe" yet. How can i circumvent this?
procedure CurStepChanged(CurStep: TSetupStep);
var
I: Integer;
LogList: TStringList;
begin
if CurStep = ssInstall then
begin
LogList := TStringList.Create;
try
LogList.Add('Selected components:');
for I := 0 to WizardForm.ComponentsList.Items.Count - 1 do
if WizardForm.ComponentsList.Checked[I] then
LogList.Add('Component: ' + WizardForm.ComponentsList.ItemCaption[I]);
LogList.SaveToFile(ExpandConstant('{app}\install.log'));
finally
LogList.Free;
end;
end;
end;
I suspect you're trying to access the folder too early in the process, before it's actually been created yet.
Try changing to a later step in the process, such as ssPostInstall. At that point, you'll know for certain that the folder has been created. The rest of your code should be able to stay the same.
procedure CurStepChanged(CurStep: TSetupStep);
var
I: Integer;
LogList: TStringList;
begin
if CurStep = ssPostInstall then
begin
LogList := TStringList.Create;
try
LogList.Add('Selected components:');
for I := 0 to WizardForm.ComponentsList.Items.Count - 1 do
if WizardForm.ComponentsList.Checked[I] then
LogList.Add('Component: ' + WizardForm.ComponentsList.ItemCaption[I]);
LogList.SaveToFile(ExpandConstant('{app}\install.log'));
finally
LogList.Free;
end;
end;
end;

Resources