Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Load Log.txt From other apps to Memo - Delphi7

Tags:

delphi

memo

I am trying to record the session log from other applications (Proxifier) to a Memo. I've tried using the command :

procedure TForm1.TimerTimer(Sender: TObject);
begin
  Memo1.Lines.LoadFromFile('C:\PMASSH\Proxyfier\Profiles\Log.txt');
end;

but at certain times I get an error

enter image description here

Can you help my problem above ? I would really appreciate of all the answers.

Thanks

like image 598
Pujangga Adhitya Avatar asked Nov 23 '12 17:11

Pujangga Adhitya


3 Answers

The other program has opened the file with a sharing mode that does not allow other processes to read it. Typically this happens when the other application is writing to the file.

There's not a whole lot you can do about this. This is perfectly normal behaviour, and is to be expected. You can try detecting the error, waiting for a short period of time, and re-trying.

Since you are already running this on a timer, the re-try will just happen. So perhaps you just need to suppress those exceptions:

procedure TForm1.TimerTimer(Sender: TObject); 
begin
  try
    Memo1.Lines.LoadFromFile(...);
  except
    on EFOpenError do
      ; //swallow this error
  end;
end;

Note that detecting EFOpenError is perhaps a little crude. Perhaps there are other failure modes that lead to that error. However, as a first pass, the code above is a decent start.

like image 108
David Heffernan Avatar answered Nov 14 '22 23:11

David Heffernan


David's answer is correct. I just want to clarify why this is happening.

The answer lies in the code:

procedure TStrings.LoadFromFile(const FileName: string);
var
  Stream: TStream;
begin
  Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;

as you can see, the file is accessed for sharing but no writing is allowed. you can solve this by creating the filestream yourself:

Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);

and then use the Lines.LoadFromStream() method to load the contents into the memo

Please note that the problem may subsist in cases where the other application has opened the file in exclusive mode (i.e. no sharing), so proper Exception management like in David's answer is still needed.

like image 5
whosrdaddy Avatar answered Nov 14 '22 22:11

whosrdaddy


You can try your luck with ReadFile WinAPI. On a shared read open mode, you'll be able to sneak and read the contents of the file at last file buffer flush. If that another application (Proxifier) opened the file with CreateFile WinAPI with FILE_SHARE_READ share mode then you'll be able to open it for reading, as long as you use ReadFile API. Standart LoadFromFile method won't work here if it still was opened for share, and you'll get the same 'lock' error.

But here's the catch.. You'll have to deal with buffers, sizes and handles... You'll have to assing a handle to file for reading, get the file size with that handle, set an array with that size, do read to that array and assign, add (whatever) that array to the memo.. Pure usage of WinAPI. Some job for a simple task...

Here is a basic example of how to deal with files with WinAPI:

The key assumption of that other application's file open process:

var
  Form1: TForm1;
  logfile: Textfile;
  h: THandle;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin    
  // AssignFile(logfile, 'c:\deneme.txt');
  // Rewrite(logfile);

  h := CreateFile('C:\deneme.txt', GENERIC_WRITE, FILE_SHARE_READ, nil,
    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

  Timer1.enabled := true;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Timer1.enabled := false;

  // CloseFile(logfile);
  CloseHandle(h);
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  str: AnsiString;
  p: pointer;
  buf: array of ansichar;
  written: cardinal;
begin
  // Writeln(logfile, 'denemeStr');

  str := 'denemeStr' + #13#10;
  p := pansichar(str);

  SetLength(buf, length(str));
  move(p^, buf[0], length(str));

  WriteFile(h, buf[0], length(buf), written, nil);
  FlushFileBuffers(h);
end;

And if it's been shared for reading, this is how you can read from it:

procedure TForm1.Button1Click(Sender: TObject);
var
  h: THandle;
  buf: array of ansichar;
  size, read: cardinal;
begin
  Memo1.Lines.Clear;
  // Memo1.Lines.LoadFromFile('c:\deneme.txt');

  h := CreateFile('C:\deneme.txt', GENERIC_READ, FILE_SHARE_READ, nil,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  size := GetFileSize(h, nil);
  SetLength(buf, size);

  ReadFile(h, buf[0], size, read, nil);
  CloseHandle(h);
  Memo1.Lines.Add(pansichar(buf));
end;

Hope this'd help...

like image 2
Hasan Manzak Avatar answered Nov 14 '22 22:11

Hasan Manzak