Accessing Text File from Service - delphi

I'm tryin to write a simple service in Delphi XE7, in a Win7 64bit virtual machine where Delphi is installed.
All what i need for now is to open/create a text file and write something into it every second. It should be simple ... should ...
Immediately after creating the service, and installing it, it runs well.
I addedd this code:
const
LogName = 'C:\GFLog.txt';
var FLogFile : TextFile;
procedure TServiceTest.ServiceExecute(Sender: TService);
begin
ServiceThread.ProcessRequests(False);
try
AssignFile(FLogFile, LogName);
if not FileExists(LogName)
then Append(FlogFile)
else Rewrite(FlogFile);
WriteLn(FLogFile,'Start '+TimeToStr(Now));
while not Terminated do
begin
WriteLn(FLogFile,TimeToStr(Now));
Sleep(1000);
ServiceThread.ProcessRequests(False);
end;
CloseFile(FLogFile);
except
on E:Exception do
ShowMessage(E.Message)
end;
end;
As a result, the file is not created, no errors are shown, and I can't understand why.
Of course, I've made something wrong, but what?
Someone can help?

Ok, solved ... copy and paste error ... i feelVERY stupid, but ...
if not FileExists(LogName)
then Append(FlogFile)
else Rewrite(FlogFile);
I was tryin to append to not exixting file ...

Related

How to set the working directory using JCL's TJclCommandLineTool?

I'm using the JCL Delphi library in my project. Below is my code.
// uses JclSysUtils;
var cmd := TJclCommandLineTool.Create(parts[0]);
if cmd.Execute(params) then
begin
...
end;
As you can see, I use TJclCommandLineTool to invoke an external command line program. The problem is that the external program is always running in the current directory that I started my application. I want to know how can I pass a custom working directory to the external program. JCL's document is pretty lame and I can't find related information.
TJclCommandLineTool does not have options like this. So easiest way to do it:
//uses System.IOUtil;
var oldDir := TDirectory.GetCurrentDirectory;
try
TDirectory.SetCurrentDirectory(ANewProgramDir);
var cmd := TJclCommandLineTool.Create(parts[0]);
if cmd.Execute(params) then
begin
// ...
end;
finally
TDirectory.SetCurrentDirectory(oldDir);
end;

How to solve [FireDAC][Phys][SQLite] ERROR: unable to open database file, When app is installed in programFiles?

I developed a tool using Firedac with the database as SQLite.
after finishing the project and making an installer (InnoSetup) I get an error
[FireDAC][Phys][SQLite] ERROR: unable to open database file
when I launch the app (double click).
This is the connection parameters I use
constructor TDbInteract.Create(const aDatabasePath: string; const aOnNeedCredentials: TOnNeedCredentials);
var
aParams: array of string;
begin
if not TFile.Exists(aDatabasePath) then
raise Exception.Create('Database file not found');
aParams := ['DriverID=SQLite',
'Database=' + aDatabasePath,
'OpenMode=CreateUTF16',
'LockingMode=Normal',
'JournalMode=WAL',
'StringFormat=Unicode',
'Synchronous=Full',
'UpdateOptions.LockWait=True',
'BusyTimeout=30000',
'SQLiteAdvanced=temp_store=MEMORY;page_size=4096;auto_vacuum=FULL'];
InitiateResource(aParams, aOnNeedCredentials);
end;
procedure TDbInteract.InitiateResource(const aParams: array of string; const aOnNeedCredentials: TOnNeedCredentials);
var
I: Integer;
Credentials: TStringDynArray;
begin
FRowsAffected := 0;
FIsForeignKeyHonored := True;
FOwnsResultDataSets := True;
FDataSetContainer := TDataSetContainer.Create(nil);
FConnection := TFDConnection.Create(nil);
try
for I := Low(aParams) to High(aParams) do
begin
FConnection.Params.Add(aParams[I]);
end;
if Assigned(aOnNeedCredentials) then
begin
aOnNeedCredentials(Self, Credentials);
for I := Low(Credentials) to High(Credentials) do
begin
FConnection.Params.Add(Credentials[I]);
end;
end;
FConnection.Open;
except
raise;
end;
end;
**Identified problems:
I read somewhere (do not remember the page I was in) that SQLite engine requires full lock on the directory that it wants to write to. and this is the problem. How ever I run the tool as invoker and my account is an admin so that is not a problem. Also I have the same tool written in c# and this problem never occurs.
Solutions I found:
Run the tool as administrator
Do not Install the tool in ProgramFiles directory
I really don't like these solutions. and would like to run my tool from program Files directory as it is part of a bigger project.
Note: The database file is in programdata directory. It is created by the tool (this works).
Edit: I just tried putting the DB file in C:\Users\Nacereddine\AppData\Roaming\MyTool And I still have the same problem when the tool is installed in C:\Program Files (x86)\MyTool
This how I create the DB file
class procedure TDbInteract.CreateSQLiteDb(const aDatabasePath: string; const aTables: TStringDynArray);
var
I: Integer;
aParams: array of string;
aConnection: TFDConnection;
begin
aParams := ['DriverID=SQLite',
'Database=' + aDatabasePath,
'OpenMode=CreateUTF16',
'LockingMode=Normal',
'JournalMode=WAL',
'StringFormat=Unicode',
'Synchronous=Full',
'UpdateOptions.LockWait=True',
'BusyTimeout=30000',
'SQLiteAdvanced=temp_store=MEMORY;page_size=4096;auto_vacuum=FULL'];
aConnection := TFDConnection.Create(nil);
try
for I := Low(aParams) to High(aParams) do
begin
aConnection.Params.Add(aParams[I]);
end;
aConnection.Open();
for I := Low(aTables) to High(aTables) do
begin
aConnection.ExecSQL(aTables[I]);
end;
finally
aConnection.Close;
aConnection.Free;
end;
end;
Note: I do not know if this makes any difference but the Db file is encrypted.
Sorry for the trouble folks.
The problem was that we had a Localization db file installed with the tool in ProgramFiles.
What made me exclude that from my investigation is that, when opening this file I set the OpenMode to ReadOnly
FConnection.Params.Add('OpenMode=ReadOnly');
but as I said before in my question SQLite engine requires full access to the folder containing the db file so it preforms a lock on it (still did not find the page I read this on).
I checked this by playing around with open modes and debugging the tool each time.
once I changed the permissions of both the file and the directory the error was gone.
at the end I decided to move the localization file to the programData directory with the main db file and all is well.
I realized (Thank you for this #Ken and #David) that the programData directory also requires admin permissions to write to, and therefore I will move the db files to a more appropriate dir (i.e Users).
What is useful from this problem is that even if you connect to the Sqlite db file with OpenMode=ReadOnly, you still need write access for the path to that file.

TMemIniFile.Create hanging when called in ServiceStart

In a service running under system account the code below hangs in the TMemIniFile.Create without errors.
If we replace it with TIniFile, it works fine.
It's a Delphi Tokyo 10.2.3 Win32 app running under Windows Server 2012R2. There's no concurrent access to the INI file.
This is the first time (first client) we see this, it has been running fine on many machines.
I have no idea what to look for further. Any ideas?
It 'works' now because we switched to TIniFile, but I'd like to find the cause. From other posts I read here, TINIfile seems to be more finicky than TMemINIfile, my situation is the reverse.
There are no special characters in the INI file and it is created with an ASCII editor.
// This is set in the ServiceCreate:
FIniFileNaam := ChangeFileExt(ParamStr(0),'.INI');
// This is called from the ServiceStart:
procedure TTimetellServiceBase.LeesINI;
var lIniFile : TMemIniFile;
begin
LogMessage(FIniFileNaam, EVENTLOG_INFORMATION_TYPE, cCatInfo, cReadINI); // Logs to event log, we see this
FStartDir := ExtractFilePath(ParamStr(0));
if assigned(FLaunchThreadLijst) then FreeAndNil(FLaunchThreadLijst);
FLaunchThreadLijst := TStringList.Create;
try
if FileExists(FIniFileNaam) then
begin
// Lees waarden uit INI file
lIniFile := TMemIniFile.Create(FIniFileNaam); // This is the call that hangs. The service is unresponsive now.
try
FLaunchThreadLijst.CommaText := lIniFile.ReadString(INISECTIE_SERVICETASKS,'RunIniFiles','');
FMaxTaskDuration := lIniFile.ReadInteger(INISECTIE_SERVICETASKS,'MaxTaskDuration',FMaxTaskDuration);
finally
FreeAndNil(lIniFile);
end;
end;
finally
if (FLaunchThreadLijst.Count = 0) and FileExists(FStartDir + FExeName) then
FLaunchThreadLijst.Add(SDEFAULTTHREADNAME);
LogMessage(Format('FLaunchThreadLijst.CommaText: %s (%d items)',[FLaunchThreadLijst.CommaText,FLaunchThreadLijst.Count]), EVENTLOG_INFORMATION_TYPE, cCatInfo, cLaunchList);
end;
end;
FWIW, INI file contents:
[TASKMANAGER]
RunIniFiles=TTTasks.ini
MaxTaskDuration=2
RestartIniFiles=
KillIniFiles=

Create and/or Write to a file

I feel like this should be easy, but google is totally failing me at the moment. I want to open a file, or create it if it doesn't exist, and write to it.
The following
AssignFile(logFile, 'Test.txt');
Append(logFile);
throws an error on the second line when the file doesn't exist yet, which I assume is expected. But I'm really failing at finding out how to a) test if the file exists and b) create it when needed.
FYI, working in Delphi XE.
You can use the FileExists function and then use Append if exist or Rewrite if not.
AssignFile(logFile, 'Test.txt');
if FileExists('test.txt') then
Append(logFile)
else
Rewrite(logFile);
//do your stuff
CloseFile(logFile);
Any solution that uses FileExists to choose how to open the file has a race condition. If the file's existence changes between the time you test it and the time you attempt to open the file, your program will fail. Delphi doesn't provide any way to solve that problem with its native file I/O routines.
If your Delphi version is new enough to offer it, you can use the TFile.Open with the fmOpenOrCreate open mode, which does exactly what you want; it returns a TFileStream.
Otherwise, you can use the Windows API function CreateFile to open your file instead. Set the dwCreationDisposition parameter to OPEN_ALWAYS, which tells it to create the file if it doesn't already exist.
You should be using TFileStream instead. Here's a sample that will create a file if it doesn't exist, or write to it if it does:
var
FS: TFileStream;
sOut: string;
i: Integer;
Flags: Word;
begin
Flags := fmOpenReadWrite;
if not FileExists('D:\Temp\Junkfile.txt') then
Flags := Flags or fmCreate;
FS := TFileStream.Create('D:\Temp\Junkfile.txt', Flags);
try
FS.Position := FS.Size; // Will be 0 if file created, end of text if not
sOut := 'This is test line %d'#13#10;
for i := 1 to 10 do
begin
sOut := Format(sOut, [i]);
FS.Write(sOut[1], Length(sOut) * SizeOf(Char));
end;
finally
FS.Free;
end;
end;
If you are just doing something simple, the IOUtils Unit is a lot easier. It has a lot of utilities for writing to files.
e.g.
procedure WriteAllText(const Path: string; const Contents: string);
overload; static;
Creates a new file, writes the specified string to the file, and then
closes the file. If the target file already exists, it is overwritten.
You can also use the load/save feature in a TStringList to solve your problem.
This might be a bad solution, because the whole file will be loaded into memory, modified in memory and then saved to back to disk. (As opposed to your solution where you just write directly to the file). It's obviously a bad solution for multiuser situations.
But this approach is OK for smaller files, and it is easy to work with and easy understand.
const
FileName = 'test.txt';
var
strList: TStringList;
begin
strList := TStringList.Create;
try
if FileExists(FileName) then
strList.LoadFromFile(FileName);
strList.Add('My new line');
strList.SaveToFile(FileName);
finally
strList.Free;
end;
end;

Cannot make sense out a Delphi windows file name

I am trying to copy from a file X to this name
C:\RIP2\France Clidat\Les Plus Belles Oeuvres - France Clidat\(01)3_ Un Sospiro.flac
I have checked that there is no bad characters, If I force directorires it creates
C:\RIP2\France Clidat\Les Plus Belles Oeuvres - France Clidat
but it refuses to write the file and I do not understand why a simple test
procedure foo(str: string);
var
f:File;
begin
Assign(f,str);
Rewrite(f);
CloseFile(f);
end;
will crash saying it is not a valid file name but it is!
If I remove ALL blank spaces it works
I am lost please Help
Since command line copies usually require wrapping double quotes around the file name, I'm wondering if the API would need something similar. Maybe try single or double quotes to see if that solves the problem?
Try to make sure your directories are there before you try to create the file in them.
I tried with D2010 on Win7 and it worked with ForceDirectories:
const
sFilename = 'C:\RIP2\France Clidat\Les Plus Belles Oeuvres - France Clidat\(01)3_ Un Sospiro.flac';
procedure foo(str: string);
var
f: File;
begin
if not ForceDirectories(ExtractFileDir(str)) then
showMessage('ForceDirectories failed')
else
begin
AssignFile(f,str);
Rewrite(f);
CloseFile(f);
end;
end;
procedure TForm10.Button1Click(Sender: TObject);
begin
foo(sFilename);
end;
Try some tests to see if it's objecting to the parens, underscore, spaces, etc.. Then you'll know more.
Call GetLastError to find out if you're throwing an error. Windows may be trying to tell you something, and your code isn't listening.

Resources