PDA

Ver la Versión Completa : Tail -f en delphi


paquechu
21-03-2013, 21:25:31
Buenas,
Aunque llevaba bastante tiempo sin programar ya en delphi, mi jefe me ha pedido que me ponga el mono de trabajo de nuevo :-)
Estoy un poco pegao con lo que tengo que hacer y es por lo que busco vuestra ayuda.

Se trata de lo siguiente: Debo hacer un programa que inspeccione un archivo de log abierto por otra aplicación y al que ésta última le esta añadiendo líneas contínuamente (hay rotación de logs diaria).
Por cada línea debo realizar un análisis por temas de seguridad.
No se muy bien por donde empezar.
Me podeis echar una mano por favor?
Muchas gracias

ecfisa
21-03-2013, 22:17:48
Hola paquechu.

Si tenes acceso al código fuente de la aplicación que genera el archivo (log), podes enviar la última línea generada mediante la estructura COPYDATASTRUCT (http://msdn.microsoft.com/en-us/library/windows/desktop/ms649011%28v=vs.85%29.aspx) y luego capturar el mensaje WM_COPYDATA (http://msdn.microsoft.com/en-us/library/windows/desktop/ms649011%28v=vs.85%29.aspx) desde la aplicación que monitorea.

En estos enlaces tenes algunos ejemplos:

que evento usar? (http://www.clubdelphi.com/foros/showthread.php?t=68691)
que en lblmarquee se pueda copiar un renglon (http://www.clubdelphi.com/foros/showthread.php?t=69609)
Lamar una aplicación y ejecutar acciones (http://www.clubdelphi.com/foros/showthread.php?t=81589)



Saludos. :)

paquechu
21-03-2013, 22:45:32
Hola ecfisa,
Pues lo cierto es que no tengo acceso al codigo de la aplicación.
Se trata de logs generados en formato texto por un servidor ISA Server de Microsoft.
Debo localizar el archivo, abrirlo y empezar a leer desde el final línea a línea...
Saludos y gracias :-)

ecfisa
22-03-2013, 00:09:06
Hola paquechu.

Entonces, en principio depende de con que "sharing options" haya abierto el archivo la otra aplicacion.

¿ Ya intentaste abrirlo para lectura, estando activa la aplicación generadora y te mostró algún error ?

Saludos. :)

paquechu
22-03-2013, 14:04:48
Hola de nuevo,
Pues no, la verdad es que no se muy bien como enfocarlo, más que por el modo en el que está abierto el archivo por como leer el archivo que se está alimentando constantemente por otra aplicación y gestionar esas entradas en vivo.
Saludos.

ecfisa
22-03-2013, 15:00:46
Hola paquechu.


Para leer el archivo de texto podes hacer:
1)

var
F : TextFile;
str: string;
begin
AssignFile(F, FNAME);
Reset(F);
while not Eof(F) do
begin
Readln(F,str);
// aquí lo que hagas con str
end;
CloseFile(F);
end;


2)

var
SL: TStrings;
begin
SL := TStringList.Create;
try
SL.LoadFromFile(FNAME);
// aquí lo que hagas con SL[n]
finally
SL.Free
end
end;


Un ejemplo que devuelve la última línea del archivo log:

function GetLastLine(const aFileName: string): string;
var
FS : TFileStream;
buf : Char;
i : Integer;
begin
FS := TFileStream.Create(aFileName, fmOpenRead);
try
Result := '';
i := FS.Size + 2;
repeat
FS.Seek(FS.Size-i, soEnd);
FS.Read(buf, SizeOf(buf));
Insert(buf, Result, 1);
Inc(i);
until buf = #10;
finally
FS.Free;
end;
end;

// llamada ejemplo
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(GetLastLine('C:\ARCHIVO_LOG.LOG'))
end;

De este modo es mucho mas rápido que leer todo el archivo si te decidis por obtener datos con, por ejemplo, un TTimer.

Pero el último código es sólo una aproximación, con muchos supuestos, podría funcionar o no dependiendo del formato con que guarda las líneas la aplicación. Sería muy útil si pudieras poner textualmente (copiar/pegar) un trozo del texto que se genera en el archivo log.

Ahora si lo que estas buscando es obtener la última línea en tiempo real y la aplicación generadora no envía ningún mensaje cuando terminó de guardar una línea, no conozco como lograrlo.

Espero que alguna opción te sea útil.

Saludos.:)

mamcx
22-03-2013, 15:59:07
He echado una mirada en como se hace en python:

http://code.activestate.com/recipes/157035-tail-f-in-python/

y veo que es necesario ademas usar sleep (muy corto) para poder leer de forma continua.

paquechu
22-03-2013, 16:22:58
Hola ecfisa,
Si, conocia los metodos señalados en los puntos 1 y 2.
Mi consulta va mas en la línea de lo que indicas en el punto 3, en la parte de identificar cuando se genera una nueva línea en el archivo (en tiempo real) y entonces leer la linea completa y procesarla.
Es un buen comienzo el que me indicas asi que empezaré por ahí :-)
Gracias ecfisa.

paquechu
22-03-2013, 16:26:37
Hola mamcx,
tomo nota del retardo necesario aunque de phyton no tengo ni papa, je,je

Al final de la pagina que referencias aparece este codigo, si controlas delphy y phyton podrias orientarme en la adaptación del código?:
Muchas gracias.
def tail_f(file):
interval = 1.0

while True:
where = file.tell()
line = file.readline()
if not line:
time.sleep(interval)
file.seek(where)
else:
yield line
Which then allows you to write code like:
for line in tail_f(open(sys.argv[1])):
print line,

nlsgarcia
23-03-2013, 03:18:10
paquechu,


...Debo hacer un programa que inspeccione un archivo de log abierto por otra aplicación y al que ésta última le esta añadiendo líneas contínuamente.....Debo localizar el archivo, abrirlo y empezar a leer desde el final línea a línea...


Revisa este código:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;

type
TForm1 = class(TForm)
Timer1: TTimer;
ListBox1: TListBox;
procedure Timer1Timer(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

function GetFileLastLine(const Filename : String) : String;
var
StrFile : TStringList;
begin
try
StrFile := TStringList.Create;
StrFile.LoadFromFile(Filename);
Result := StrFile[StrFile.Count-1];
except
on E: Exception do
begin
MessageDlg(E.Message, mtError, [mbOk],0);
Result := '';
end;
end;
StrFile.Free;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
ListBox1.Items.Add(GetFileLastLine('C:\Test_File.txt'));
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
Timer1.Interval := 100;
end;

end.

El código anterior lee de forma continua por medio de un control TTimer cada 100 ms la última línea de un archivo de texto y la adiciona a un control TListBox para su posterior procesamiento.

Nota: Este código fue probado con un archivo de texto de 2.000.000 de líneas de forma satisfactoria.

Espero sea útil :)

Nelson.

mamcx
23-03-2013, 15:19:07
Seria bueno almacenar cuantas lineas habian en la lectura anterior y luego restarlas, porque un log puede recibir varias lineas nuevas mientras esta el timer esperando.

paquechu
24-03-2013, 12:47:31
Vaya!, perdonad, estaba enfrascado con el tema de marras todo este tiempo....
Le he echado un vistazo al código.
Dices que puedes cargar 2.000.000 de lineas en el tstringlist, aunque no se el tamaño maximo (no de lineas) sino de memoria que se puede cargar con un tstringlist.
Lo voy a probar, me preocupa el tiempo de carga y el consumo excesivo de memoria.
Muchas gracias :-)

paquechu
24-03-2013, 12:58:00
Hola de nuevo,
Os pego el trozo de codigo (aparecen variables declaradas globalmente) en el que estoy trabajando
que de momento parece que me funciona aunque seguro que se puede mejorar/optimizar, pero es por donde voy :-)


procedure TfrmPrinc.bProcesarClick(Sender: TObject);
var
vLinea:String;
begin
bSalir:=False;
PosUltimaLinea:=0;
while Not bSalir do
begin
vLinea:=GetLastLine(sDirTrab+'\'+sArchLog);
mLog.lines.add(vLinea);
// Aqui procesar la línea
end;
end;

function TfrmPrinc.GetLastLine(const aFileName: string): string;
var
buf : Char;
i,j,PosiLinea : LongInt;

begin
FSPrin := TFileStream.Create(aFileName, fmOpenRead + fmShareDenyNone );
try
bLinea:=False;
while Not bLinea do
begin
// Ir al final de fichero
i := FSPrin.Size-nCarLi;
PosUltimaAux:=i+nCarLi;
if PosUltimaAux<>PosUltimaLinea then
begin
try
PosUltimaLinea:=PosUltimaAux;
buf:=#0;
FSPrin.Seek(i, soBeginning);
FSPrin.Read(buf, SizeOf(buf));
if (buf = #10) or (buf = #13) then
begin
bLinea:=True;
PosiLinea:=i;
end;
except;
end;
Application.ProcessMessages;
if bSalir then begin break; exit; end;
end;
Application.ProcessMessages;
if bSalir then begin break; exit; end;
Sleep(strtoint(dfRetardo.Text));
end;
// Se localiza retorno de carro al final del archivo y se entiende
// que hay una línea.
// Hay que leer la linea hacia atras hasta encontrar retorno de carro
// de la anterior linea o inicio de fichero.

Result := '';
Buf:=#0;
i := 1;
while (buf <> #10) and (buf <> #13) and (FSPrin.position<>0) do
begin
try
buf:=#0;
//FSPrin.Seek(PosiLinea-i, soBeginning);
FSPrin.Position:=PosiLinea-i;
FSPrin.Read(buf, SizeOf(buf));
if (buf <> #10) and (buf <> #13) then
Insert(buf, Result, 1);
Inc(i);
except;
end;
Application.ProcessMessages;
if bSalir then exit;
end;
finally
FSPrin.Free;
end;
end;

Julián
24-03-2013, 19:30:00
Para eso lo mejor puede ser usar el api de windows, que permite colgar un hook o como se llame que "avisa" cada vez que se modifique el archivo que quieras. No me acuerdo que función es, pero existe.


EDITO: Una busqueda en google por "delphi file notification" devielve un motón de entradas parecidas a esta: http://www.delphi-central.com/components/FileNotification.aspx

paquechu
24-03-2013, 22:25:38
Gracias Julian, lo estudiaré :-)
Saludos.

nlsgarcia
24-03-2013, 22:46:14
paquechu,


...no se el tamaño maximo (no de lineas) sino de memoria que se puede cargar con un tstringlist...

Un String en Delphi 7 (AnsiString, WideString) tiene un tamaño máximo de 2 GB, revisa este link: Limitation of TStringList. (http://edn.embarcadero.com/article/30333)


...me preocupa el tiempo de carga y el consumo excesivo de memoria...

EL código propuesto debería funcionar correctamente en archivos de texto que no sean de un tamaño notable (Logs rotativos), el ejemplo mencionado de 2.000.000 de lineas de texto fue solo a efectos de prueba de la función.

Te sugiero revisar detalladamente el código del Msg #6 (function GetLastLine), el código es altamente eficiente dado que solo obtiene la última línea del archivo de forma directa, lo cual la hace ideal como parte de un control TTimer y sin sobrecarga de recursos. Como comentario, funciono de forma optima con el archivo de pruebas mencionado.

Espero sea útil :)

Nelson.

ecfisa
25-03-2013, 04:49:47
Hola paquechu.

Dado que la aplicación que genera el log añade líneas, este cambiará su tamaño. Entonces, una forma de detectar si se agregó una linea usando un TTimer, podría ser:

...
private
FPreviousSize: Int64; // Ultimo tamaño monitoreado
end;

...

implementation

const
NOMARCH = 'C:\LOG.TXT'; // Ruta hasta y nombre del archivo

(* Obtener tamaño del archivo *)
function FileLongSize(const aFileName: string): Int64;
var
FindData: TWin32FindData;
begin
Windows.FindClose(FindFirstFile(PChar(aFileName), FindData));
Result := FindData.nFileSizeHigh shl 32 + FindData.nFileSizeLow;
end;

(* Obtener última línea *)
function GetLastLine(const aFileName: string): string;
var
FS : TFileStream;
buf : Char;
i : Integer;
begin
FS := TFileStream.Create(aFileName, fmOpenRead);
try
Result := '';
i := FS.Size + 2;
repeat
FS.Seek(FS.Size-i, soEnd);
FS.Read(buf, SizeOf(buf));
Insert(buf, Result, 1);
Inc(i);
until (buf = #10);
finally
FS.Free;
end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
// Tamaño al inicio del monitoreo
FPreviousSize:= FileLongSize(NOMARCH);
//Timer1.Interval := ??? (a tu necesidad)
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
NewSize: Int64;
begin
NewSize:= FileLongSize(NOMARCH); // Nuevo tamaño
if FPreviousSize <> NewSize then
begin
FPreviousSize:= NewSize;
Timer1.Enabled:= False;
// Aca lo que gustes hacer con la última línea, para el ejemplo la muestro.
MessageBox(Handle,PChar(GetLastLine(NOMARCH)),'NUEVA LINEA',MB_ICONEXCLAMATION);
Timer1.Enabled:= True;
end;
end;
...

No sé si es la forma mas optima, pero es simple y en mis pruebas funciona. Hasta que encuentres algo mejor tal vez te sirva...

Saludos.

paquechu
25-03-2013, 09:00:46
Hola nlsgarcia,
Gracias por las aclaraciones, respecto al codigo del mensaje numero 6 (GetLastLine), efectívamente, es el que he tomado como base y he logrado adaptar a la idea que iba buscando.
Muchas gracias :-)

paquechu,


Un String en Delphi 7 (AnsiString, WideString) tiene un tamaño máximo de 2 GB, revisa este link: Limitation of TStringList. (http://edn.embarcadero.com/article/30333)


EL código propuesto debería funcionar correctamente en archivos de texto que no sean de un tamaño notable (Logs rotativos), el ejemplo mencionado de 2.000.000 de lineas de texto fue solo a efectos de prueba de la función.

Te sugiero revisar detalladamente el código del Msg #6 (function GetLastLine), el código es altamente eficiente dado que solo obtiene la última línea del archivo de forma directa, lo cual la hace ideal como parte de un control TTimer y sin sobrecarga de recursos. Como comentario, funciono de forma optima con el archivo de pruebas mencionado.

Espero sea útil :)

Nelson.

paquechu
25-03-2013, 09:05:21
Hola Ecfisa,
COmo comentaba en mi anterior mensaje, el código que he utilizado se basa en tu funcion GetLastLine.
Ya veo que lo has mejorado.
Lo voy a probar (aunque la adaptación que os puse también me funciona).
Un saludo y gracias.

Hola paquechu.

Dado que la aplicación que genera el log añade líneas, este cambiará su tamaño. Entonces, una forma de detectar si se agregó una linea usando un TTimer, podría ser:

Código Delphi [-] (http://www.clubdelphi.com/foros/#)
...
private
FPreviousSize: Int64; // Ultimo tamaño monitoreado
end;

...

implementation

const
NOMARCH = 'C:\LOG.TXT'; // Ruta hasta y nombre del archivo

(* Obtener tamaño del archivo *)
function FileLongSize(const aFileName: string): Int64;
var
FindData: TWin32FindData;
begin
Windows.FindClose(FindFirstFile(PChar(aFileName), FindData));
Result := FindData.nFileSizeHigh shl 32 + FindData.nFileSizeLow;
end;

(* Obtener última línea *)
function GetLastLine(const aFileName: string): string;
var
FS : TFileStream;
buf : Char;
i : Integer;
begin
FS := TFileStream.Create(aFileName, fmOpenRead);
try
Result := '';
i := FS.Size + 2;
repeat
FS.Seek(FS.Size-i, soEnd);
FS.Read(buf, SizeOf(buf));
Insert(buf, Result, 1);
Inc(i);
until (buf = #10);
finally
FS.Free;
end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
// Tamaño al inicio del monitoreo
FPreviousSize:= FileLongSize(NOMARCH);
//Timer1.Interval := ??? (a tu necesidad)
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
NewSize: Int64;
begin
NewSize:= FileLongSize(NOMARCH); // Nuevo tamaño
if FPreviousSize <> NewSize then
begin
FPreviousSize:= NewSize;
Timer1.Enabled:= False;
// Aca lo que gustes hacer con la última línea, para el ejemplo la muestro.
MessageBox(Handle,PChar(GetLastLine(NOMARCH)),'NUEVA LINEA',MB_ICONEXCLAMATION);
Timer1.Enabled:= True;
end;
end;
...




No sé si es la forma mas optima, pero es simple y en mis pruebas funciona. Hasta que encuentres algo mejor tal vez te sirva...

Saludos.

paquechu
25-03-2013, 09:06:55
Por mi parte, me doy más que satisfecho con la acogida y ayuda que me habeis prestado y doy por cerrado este asunto.
Estaba un poco agobiadete con este tema.
Muchisimas gracias a todos :)
Un cordial saludo

Julián
25-03-2013, 12:32:09
Lo mas optimo se supone que sería usar el gestlastline que estais comentando, pero en lugar de con un ttimer, con el evento del api de windows (el que te comenté en mi mensaje anterior) que se lanza sólo cuando se modifica el fichero.

Un saludo!

paquechu
25-03-2013, 12:48:01
Hola Julian, coincido contigo totalmente.
Solo me queda una duda, que es donde falla el procedimiento que he adaptado basado en la funcion getlastline, y es la forma en la que se está generando el archivo de log.

Si al generar las líneas del archivo de log se hace un flush por cada línea generada, funciona OK, pero si el flush se hace cada ciertas líneas entonces no funciona bien. No se si este componente contemplaria este caso...

Saludos.

Lo mas optimo se supone que sería usar el gestlastline que estais comentando, pero en lugar de con un ttimer, con el evento del api de windows (el que te comenté en mi mensaje anterior) que se lanza sólo cuando se modifica el fichero.

Un saludo!

Julián
25-03-2013, 18:48:18
Hola Julian, coincido contigo totalmente.
Solo me queda una duda, que es donde falla el procedimiento que he adaptado basado en la funcion getlastline, y es la forma en la que se está generando el archivo de log.

Si al generar las líneas del archivo de log se hace un flush por cada línea generada, funciona OK, pero si el flush se hace cada ciertas líneas entonces no funciona bien. No se si este componente contemplaria este caso...




Pues yo entiendo que son dos cosas distintas:

1. Saber cuando determinado archivo *.log ha sido modificado.
2. leer la ultima(s) líneas

Para 1 puedes usarse un timer o el api de windows, y creo que hemos quedadi que lo mas elegante y correcto es el api de win.

Para 2 pues puedes echar mano de cualquier funcion de leer archivos que tenga el delphi, tal como indican algunos ejemplos que nos han puesto en este mismo hilo.

Lo que puedes hacer es poner aquí el cóodigo que te da error a ver que tal, pues con eso del flush no entiendo muy bien que es lo que te falla.

Un saludo!

paquechu
25-03-2013, 19:06:40
Hola Julián,
Correcto, son dos cosas.
En cuanto a la primera mi duda es saber si tanto el timer como la api de windows se dan por enterados de que un archivo determinado se ha modificado o no desde que se lanza una instrucción write o solo se enteran en el momento en que se hace una instrucción flush del bufer a volcar a disco. (Esto tengo pendiente de probar en cuanto pueda).

En lo referente al segundo punto, pues no hay ninguna duda todo está claro.

En el mensaje #13 puse el codigo que estoy utilizadno, que no es que me de un error, sino que simplemente no funciona bien cuando desde el momento en que se hace un write para escribir el log si no se vuelca inmediatamente la línea a disco (con un flush) el código no se entera de que se ha enviado mas datos al archivo de log.

Aqui te pongo un enlace a la función flush a ver si se entiende mejor lo que quiero comentar :-) : http://www.delphibasics.co.uk/RTL.asp?Name=Flush

Pues yo entiendo que son dos cosas distintas:

1. Saber cuando determinado archivo *.log ha sido modificado.
2. leer la ultima(s) líneas

Para 1 puedes usarse un timer o el api de windows, y creo que hemos quedadi que lo mas elegante y correcto es el api de win.

Para 2 pues puedes echar mano de cualquier funcion de leer archivos que tenga el delphi, tal como indican algunos ejemplos que nos han puesto en este mismo hilo.

Lo que puedes hacer es poner aquí el cóodigo que te da error a ver que tal, pues con eso del flush no entiendo muy bien que es lo que te falla.

Un saludo!

mamcx
25-03-2013, 19:52:35
Saber si tanto el timer como la api de windows se dan por enterados de que un archivo determinado se ha modificado o no desde que se lanza una instrucción write o solo se enteran en el momento en que se hace una instrucción flush del bufer a volcar a disco.


http://www.delphibasics.co.uk/RTL.asp?Name=Flush

Lee la descripcion de la ayuda. Medita sobre lo que dice. Alcanza la iluminación.

ecfisa
25-03-2013, 20:03:45
...
COmo comentaba en mi anterior mensaje, el código que he utilizado se basa en tu funcion GetLastLine.
Ya veo que lo has mejorado.
...
Hola paquechu.

En realidad no mejoré la función, es la misma del mensaje #6 :), sólo la integré a un TTimer para darle funcionalidad.

¿ Probaste el código del mensaje #17 y no te resultó ? Funcionó correctamente en todas mis pruebas, obteniendo en mi equipo, promedios de aprox. 850 microsegundos para conseguir la última de 100000 líneas.
Te adjunto el ejemplo debajo; la primera ejecución demorará un poco ya que creará un archivo con 100000 líneas de texto aleatorio en la carpeta donde lo ejecutes.

En cuanto a capturar la escritura en tiempo real, Delphi posee el componente TShellChangeNotifier que informa sobre las modificaciones sucedidas en determinada carpeta y de forma análoga podes obtenerlas usando API mediante la funcion SHChangeNotifyRegister (http://msdn.microsoft.com/en-us/library/windows/desktop/bb762120%28v=vs.85%29.aspx#feedback).
Pero de ambos modos se notifica sobre un cambio del archivo como tál (creacion, borrado, etc) y no sobre una alteración en los datos internos del mismo. No quiero decir con esto que no es posible conseguir la información, sino que yo no pude encontrar el modo todavía.

Julián ya te puso un enlace y la sugerencia de utilizar un hook. Tal vez tengas que encarar ese punto por ese lado.

Saludos. :)

Julián
26-03-2013, 00:38:15
¡Eso que dice ecfisa es lo que yo decía: un hook!

Es que ya no me acordaba de esas cosas, pues hace como 8 años que no uso el puto Windows, :D :D :D

paquechu
26-03-2013, 09:16:27
Buenas,

He probado el código que proponias, sin embargo al ejecutarlo sobre un archivo abierto de log se "queja" de que está abierto por otra aplicación y no puede continuar..... seguiré probando el resto de vuestras propuestas :-)
Saludos.

paquechu,


Un String en Delphi 7 (AnsiString, WideString) tiene un tamaño máximo de 2 GB, revisa este link: Limitation of TStringList. (http://edn.embarcadero.com/article/30333)


EL código propuesto debería funcionar correctamente en archivos de texto que no sean de un tamaño notable (Logs rotativos), el ejemplo mencionado de 2.000.000 de lineas de texto fue solo a efectos de prueba de la función.

Te sugiero revisar detalladamente el código del Msg #6 (function GetLastLine), el código es altamente eficiente dado que solo obtiene la última línea del archivo de forma directa, lo cual la hace ideal como parte de un control TTimer y sin sobrecarga de recursos. Como comentario, funciono de forma optima con el archivo de pruebas mencionado.

Espero sea útil :)

Nelson.

ecfisa
26-03-2013, 11:55:05
Buenas,

He probado el código que proponias, sin embargo al ejecutarlo sobre un archivo abierto de log se "queja" de que está abierto por otra aplicación y no puede continuar
Hola paquechu.

Muy probablemente tengas ese problema con cualquiera de las sugerencias de este hilo, eso es a lo que me refería en el mensaje (#4 (http://www.clubdelphi.com/foros/showpost.php?p=457372&postcount=4)).

Saludos.:)

paquechu
26-03-2013, 12:31:15
Si, eso es, pero se soluciona añadiendo el flag fmShareDenyNone en el momento de abrir el archivo
Saludos. :-)

Hola paquechu.

Muy probablemente tengas ese problema con cualquiera de las sugerencias de este hilo, eso es a lo que me refería en el mensaje (#4 (http://www.clubdelphi.com/foros/showpost.php?p=457372&postcount=4)).

Saludos.:)

paquechu
26-03-2013, 12:36:15
Ecfisa,
Por otro lado he probado el codigo que pones en el mensaje #17, pero tampoco contempla todas las líneas añadidas desde la última modificación y se "traga" algunas... sigo pensando que es lo del flush, je,je
Gracias :-)

EDITO: Otra cosa mas.... he intentado descargarme el ejemplo del mensaje #26 y no puedo, no se si será por el control de contenido de mi trabajo, pero al abrir el zip se queja de error crc (lo he intentado varias veces. lo probaré desde casa)....

ecfisa
26-03-2013, 12:44:12
Si, eso es, pero se soluciona añadiendo el flag fmShareDenyNone en el momento de abrir el archivo
Saludos. :-)
Si, es correcto, pero te lo señalaba por el comentario de tu mensaje anterior (#28).

Saludos.:)

ecfisa
26-03-2013, 12:48:31
Ecfisa,
Por otro lado he probado el codigo que pones en el mensaje #17, pero tampoco contempla todas las líneas añadidas desde la última modificación y se "traga" algunas... sigo pensando que es lo del flush, je,je
Gracias :-)
Es que el código que te sugerí no hace eso. El código devuelve la ultima línea añadida, no el conjunto de líneas agregadas desde desde la última modificacion.

Saludos.:)

paquechu
26-03-2013, 15:31:37
Acabo de hacer pruebas con el "notificador" que tiene la api de windows y creo que tampoco soluciona el problema de los cambios sufridos por un archivo en tiempo real.

He encontrado un archivo donde figura el componente y un ejemplo de uso que he probado de la siguiente forma:

En mi PC: he ejecutado el programa de demo que incluye el archivo que os dejo en el post y cuando hay una modificacion en un archivo del directorio que se elija, el cambio de tamaño de archivo se refleja bien, pero cuando hago lo mismo en el servidor donde se genera el log del isa server (que es un servidor virtual) ya no parece enterarse de los cambios.

NO se, creo que voy a investigar mas en la linea de guardar la posicion de la ultima línea leida y la próxima vez que el codigo detecte modificación leer a partir de esa posición.....

Saludos. :-)

nlsgarcia
27-03-2013, 04:03:55
paquechu,


...Acabo de hacer pruebas con el "notificador" que tiene la api de windows y creo que tampoco soluciona el problema de los cambios sufridos por un archivo en tiempo real...


Revisa este código:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, FileCtrl;

type
TForm1 = class(TForm)
BitBtn1: TBitBtn;
BitBtn2: TBitBtn;
Label1: TLabel;
ListBox1: TListBox;
procedure BitBtn1Click(Sender: TObject);
procedure BitBtn2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

TDirectoryMonitor = class(TThread)
public
DirectoryToMonitor : String;
MsgMonitor : String;
protected
procedure StatusOn;
procedure StatusOff;
procedure MsgError;
procedure EventMonitor;
procedure Execute; override;
end;

var
Form1: TForm1;
DirectoryMonitor : TDirectoryMonitor;

implementation

{$R *.dfm}

procedure TDirectoryMonitor.StatusOn;
begin
with form1 do
begin
label1.Font.Color := clBlue;
Label1.Caption := 'Thread On';
end;
end;

procedure TDirectoryMonitor.StatusOff;
begin
with form1 do
begin
label1.Font.Color := clRed;
Label1.Caption := 'Thread Off';
end;
end;

procedure TDirectoryMonitor.MsgError;
begin
MessageDlg('Error en la Ruta de Monitoreo', mtInformation, [mbOK],0);
end;

procedure TDirectoryMonitor.EventMonitor;
begin
with Form1 do
begin
ListBox1.Items.Add(DirectoryMonitor.MsgMonitor);
if ListBox1.ScrollWidth < ListBox1.Canvas.TextWidth(DirectoryMonitor.MsgMonitor) then
ListBox1.ScrollWidth := ListBox1.Canvas.TextWidth(DirectoryMonitor.MsgMonitor) + 120;
end;
end;

procedure TDirectoryMonitor.Execute;
type
PFileNotifyInformation = ^TFileNotifyInformation;

TFileNotifyInformation = record
NextEntryOffset: DWORD;
Action: DWORD;
FileNameLength: DWORD;
FileName: WideChar;
end;

const
FILE_LIST_DIRECTORY = 1;
BufferLength = 65536;

var
H: THandle;
fDirHandle: THandle;
fChangeHandle: THandle;
Filter, BytesRead: DWORD;
Offset, NextOffset: DWORD;
Buffer: array[0..BufferLength - 1] of byte;
Overlap: TOverlapped;
WaitResult: DWORD;
FileName : String;
InfoPointer: PFileNotifyInformation;
Action : string;
Attrs : Integer;

begin

FreeOnTerminate := True;

fDirHandle := CreateFile(PChar(DirectoryToMonitor),
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);

if (fDirHandle = INVALID_HANDLE_VALUE) or (FileExists(DirectoryToMonitor)) then
begin
Synchronize(MsgError);
Exit;
end;

fChangeHandle := CreateEvent(nil, FALSE, FALSE, nil);

FillChar(Overlap, SizeOf(TOverlapped), 0);

Overlap.hEvent := fChangeHandle;

Filter := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME
or FILE_NOTIFY_CHANGE_SIZE or FILE_NOTIFY_CHANGE_LAST_WRITE;

Synchronize(StatusOn);

while not Terminated do
begin
if ReadDirectoryChangesW(fDirHandle, @Buffer[0], BufferLength, TRUE, Filter,
@BytesRead, @Overlap, nil)
then
begin

WaitResult := WaitForMultipleObjects(1, @fChangeHandle, FALSE, 100);
if (WaitResult = WAIT_OBJECT_0) and (WaitResult <> WAIT_TIMEOUT) then
begin

InfoPointer := @Buffer[0];

repeat

NextOffset := InfoPointer.NextEntryOffset;
FileName := WideCharToString(@InfoPointer.FileName);

Attrs := FileGetAttr(DirectoryToMonitor + '\' + FileName);

if Attrs and faDirectory = faDirectory then
Action := 'DIRECTORY_';

if Attrs and faArchive = faArchive then
Action := 'FILE_';

if InfoPointer.Action = 1 then Action := Action + 'ACTION_ADDED';
if InfoPointer.Action = 2 then Action := 'FILE_DIRECTORY_ACTION_REMOVED';
if InfoPointer.Action = 3 then Action := Action + 'ACTION_MODIFIED';
if InfoPointer.Action = 4 then Action := Action + 'ACTION_RENAMED_OLD_NAME';
if InfoPointer.Action = 5 then Action := Action + 'ACTION_RENAMED_NEW_NAME';

MsgMonitor := Action + ': ' + FileName;

PByte(InfoPointer) := PByte(DWORD(InfoPointer) + NextOffset);

until NextOffset = 0;

FillChar(Buffer, SizeOf(Buffer), 0);
Synchronize(EventMonitor);

end;
end
else
begin
Break;
end;
end;

if fChangeHandle <> 0 then
CloseHandle(fChangeHandle);

if fDirHandle <> INVALID_HANDLE_VALUE then
CloseHandle(fDirHandle);

Synchronize(StatusOff);

end;

procedure TForm1.BitBtn1Click(Sender: TObject);
var
Directory : String;
begin
if SelectDirectory('Selección de Directorio para Monitoreo', 'C:\', Directory) then
begin
ListBox1.Clear;
DirectoryMonitor := TDirectoryMonitor.Create(True);
DirectoryMonitor.DirectoryToMonitor := Directory;
DirectoryMonitor.Resume;
BitBtn1.Enabled := False;
end
else
MessageDlg('No fue Seleccionado Ningún Directorio para Monitoreo', mtInformation, [mbOK],0);
end;

procedure TForm1.BitBtn2Click(Sender: TObject);
begin
if Assigned(DirectoryMonitor) then
begin
DirectoryMonitor.Terminate;
BitBtn1.Enabled := True;
end

end;

procedure TForm1.FormCreate(Sender: TObject);
begin
label1.Font.Color := clRed;
Label1.Caption := 'Thread Off';
end;

end.

El código anterior permite detectar cambios a nivel de Creación, Modificación y Eliminación de Archivos y Directorios dentro de un Directorio específico (Directorio objetivo de Monitoreo) y sus subdirectorios por medio de la función ReadDirectoryChangesW.

Este código fue probado en tres Máquinas Virtuales con Windows XP Professional x32, Windows 7 Professional x32 y x64 y una Máquina Física con Windows 7 Professional x32 y en todos los casos funcionó correctamente.

El código esta disponible en el siguiente link: http://terawiki.clubdelphi.com/Delphi/Ejemplos/Win-API/?download=DirectoryMonitor.rar

Nota: Una forma simple de probar el programa es monitorear el directorio C:\

Revisa estos links:

1- ReadDirectoryChangesW function : http://msdn.microsoft.com/en-us/library/aa365465%28v=vs.85%29.aspx

2- WaitForMultipleObjects function : http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025%28v=vs.85%29.aspx

3- CreateFile function : http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858%28v=vs.85%29.aspx
Espero sea útil :)

Nelson.

nlsgarcia
29-03-2013, 22:48:29
paquechu,


...Seria bueno almacenar cuantas lineas habian en la lectura anterior y luego restarlas...



...usar el api de windows, que permite colgar un hook o como se llame que "avisa" cada vez que se modifique el archivo...



...voy a investigar mas en la linea de guardar la posicion de la ultima línea leida y la próxima vez que el codigo detecte modificación leer a partir de esa posición...


Revisa este código:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, FileCtrl, StrUtils;

type
TForm1 = class(TForm)
BitBtn1: TBitBtn;
BitBtn2: TBitBtn;
Label1: TLabel;
ListBox1: TListBox;
BitBtn3: TBitBtn;
procedure BitBtn1Click(Sender: TObject);
procedure BitBtn2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure BitBtn3Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

TDirectoryMonitor = class(TThread)
public
DirectoryToMonitor : String;
FileToMonitor : String;
MsgMonitor : String;
MsgError : String;
protected
procedure StatusOn;
procedure StatusOff;
procedure DspMsgError;
procedure EventMonitor;
function GetFileLastLine(const FileName: string): string;
procedure Execute; override;
end;

var
Form1: TForm1;
DirectoryMonitor : TDirectoryMonitor;

implementation

{$R *.dfm}

// Establece el mensaje de Hilo en On.
procedure TDirectoryMonitor.StatusOn;
begin
with form1 do
begin
label1.Font.Color := clBlue;
Label1.Caption := 'Thread On';
end;
end;

// Establece el mensaje de Hilo en Off.
procedure TDirectoryMonitor.StatusOff;
begin
with form1 do
begin
label1.Font.Color := clRed;
Label1.Caption := 'Thread Off';
end;
end;

// Visualiza mensages de error.
procedure TDirectoryMonitor.DspMsgError;
begin
MessageDlg(MsgError, mtInformation, [mbOK],0);
end;

// Muestra en un control TListBox los cambios registrados en el archivo monitoreado.
procedure TDirectoryMonitor.EventMonitor;
var
Data : String;
i,p : Integer;
offset : Integer;

begin
with Form1 do
begin

offset := 1;
Data := DirectoryMonitor.MsgMonitor;
for i := 1 to Length(Data) do
begin
if Data[i] = Chr(10) then
begin
p := PosEx(Chr(10),Data,offset);
ListBox1.Items.Add(Copy(Data,offset,p-offset));
if ListBox1.ScrollWidth < ListBox1.Canvas.TextWidth(DirectoryMonitor.MsgMonitor) then
ListBox1.ScrollWidth := ListBox1.Canvas.TextWidth(DirectoryMonitor.MsgMonitor) + 120;
offset := p+1;
end;
end;

end;
end;

// Lee las n líneas adicionados a un archivo desde un valor de control de Monitoreo inicial
// El valor de control inicial es el tamaño del archivo el cual se establece al momento
// de la llamada al hilo de monitoreo.
function TDirectoryMonitor.GetFileLastLine(const FileName: string): string;
var
F1 : TFileStream;
F2 : TFileStream;
Buffer : string;
BufChar : Char;
i, pi, pf : Integer;
LastSize : LongWord;
Err : Integer;
FileControl : String;

begin
try

FileControl := ExtractFilePath(Application.ExeName) + 'FileControl.txt';

F1 := TFileStream.Create(FileControl, fmOpenReadWrite or fmShareDenyNone);
SetLength(Buffer, F1.Size);
F1.Read(Buffer[1],F1.size);
Val(Buffer, LastSize,Err);

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

pi := -1;
pf := LastSize - F2.Size;

Result := '';

// Se adicionaron n líneas al archivo monitoreado por 1 ocurrencia
if (LastSize = 0) then
begin

pf := 0;
F2.Seek(0,soFromBeginning);
while F2.Position < F2.Size do
begin
F2.Read(BufChar, 1);
Result := Result + BufChar;
end;

Buffer := Format('%.10d',[F2.Size]);
F1.Seek(0,soFromBeginning);
// Actualiza el valor de control de Monitoreo
F1.Write(Buffer[1],Length(Buffer));

end;

// Se adicionaron n líneas al archivo monitoreado por n ocurrencia
if (pf < 0) then
begin

pf := pf - 1;

repeat
F2.Seek(pi, soFromEnd);
F2.Read(BufChar, SizeOf(BufChar));
Insert(BufChar, Result, 1);
Dec(pi);
until (pi = pf);

Buffer := Format('%.10d',[F2.Size]);
F1.Seek(0,soFromBeginning);
// Actualiza el valor de control de Monitoreo
F1.Write(Buffer[1],Length(Buffer));

end;

finally

F1.Free;
F2.Free;

end;
end;

// Ejecuta el Hilo de Monitoreo a un archivo específico.
procedure TDirectoryMonitor.Execute;
type
PFileNotifyInformation = ^TFileNotifyInformation;

TFileNotifyInformation = record
NextEntryOffset: DWORD;
Action: DWORD;
FileNameLength: DWORD;
FileName: WideChar;
end;

const
FILE_LIST_DIRECTORY = 1;
BufferLength = 65536;

var
H: THandle;
fDirHandle: THandle;
fChangeHandle: THandle;
Filter, BytesRead: DWORD;
Offset, NextOffset: DWORD;
Buffer: array[0..BufferLength - 1] of byte;
Overlap: TOverlapped;
WaitResult: DWORD;
FileName : String;
InfoPointer: PFileNotifyInformation;
Action : string;
Attrs : Integer;
DateLastLine : TDateTime;

begin

FreeOnTerminate := True;

fDirHandle := CreateFile(PChar(DirectoryToMonitor),
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);

if (fDirHandle = INVALID_HANDLE_VALUE) or (FileExists(DirectoryToMonitor)) then
begin
MsgError := 'Función CreateFile: Error en la Ruta de Monitoreo';
Synchronize(DspMsgError);
Exit;
end;

fChangeHandle := CreateEvent(nil, FALSE, FALSE, nil);

FillChar(Overlap, SizeOf(TOverlapped), 0);

Overlap.hEvent := fChangeHandle;

Filter := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME
or FILE_NOTIFY_CHANGE_SIZE or FILE_NOTIFY_CHANGE_LAST_WRITE;

Synchronize(StatusOn);

while not Terminated do
begin
if not FileExists(DirectoryToMonitor + '\' + FiletoMonitor) then
begin
MsgError := 'Error en el Archivo o Directorio de Monitoreo Establecido Inicialmente';
Synchronize(DspMsgError);
Exit;
end;

if ReadDirectoryChangesW(fDirHandle, @Buffer[0], BufferLength, TRUE, Filter,
@BytesRead, @Overlap, nil)
then
begin

WaitResult := WaitForMultipleObjects(1, @fChangeHandle, FALSE, 100);
if (WaitResult = WAIT_OBJECT_0) and (WaitResult <> WAIT_TIMEOUT) then
begin

InfoPointer := @Buffer[0];

repeat

NextOffset := InfoPointer.NextEntryOffset;
FileName := WideCharToString(@InfoPointer.FileName);

if (FileToMonitor = FileName) and (InfoPointer.Action = 3) then
begin
Attrs := FileGetAttr(DirectoryToMonitor + '\' + FileName);
if (Attrs and faArchive = faArchive) then
begin
MsgMonitor := GetFileLastLine(FileToMonitor);
FillChar(Buffer, SizeOf(Buffer), 0);
Synchronize(EventMonitor);
end;
end;

PByte(InfoPointer) := PByte(DWORD(InfoPointer) + NextOffset);

until NextOffset = 0;
end;
end
else
begin
Break;
end;
end;

if fChangeHandle <> 0 then
CloseHandle(fChangeHandle);

if fDirHandle <> INVALID_HANDLE_VALUE then
CloseHandle(fDirHandle);

Synchronize(StatusOff);

end;

// Inicia la función de monitoreo a un archivo específico
procedure TForm1.BitBtn1Click(Sender: TObject);
var
Directory : String;
openDialog : TOpenDialog;
F1 : TFileStream;
F2 : TFileStream;
Buffer : String;
FileControl : String;

begin

openDialog := TOpenDialog.Create(self);
openDialog.InitialDir := GetCurrentDir;
openDialog.Options := [ofFileMustExist];
openDialog.Filter := 'Archivo a Monitorer|*.txt';
openDialog.FilterIndex := 1;

if openDialog.Execute then
begin

try

FileControl := ExtractFilePath(Application.ExeName) + 'FileControl.txt';
F1 := TFileStream.Create(FileControl, fmCreate);
F2 := TFileStream.Create(openDialog.FileName, fmOpenRead or fmShareDenyNone);
Buffer := Format('%.10d',[F2.Size]);
// Establece el valor de control de monitoreo inicial
F1.Write(Buffer[1],Length(Buffer));

ListBox1.Clear;
DirectoryMonitor := TDirectoryMonitor.Create(True);
DirectoryMonitor.DirectoryToMonitor := ExtractFileDir(openDialog.FileName);
DirectoryMonitor.FileToMonitor := ExtractFileName(openDialog.FileName);
DirectoryMonitor.Resume;
BitBtn1.Enabled := False;

finally

F1.Free;
F2.Free;

end;

end
else
MessageDlg('No fue Seleccionado Ningún Archivo para Monitoreo', mtInformation, [mbOK],0);

openDialog.Free;

end;

// Finaliza la función de monitoreo a un archivo específico
procedure TForm1.BitBtn2Click(Sender: TObject);
begin
if Assigned(DirectoryMonitor) then
begin
DirectoryMonitor.Terminate;
BitBtn1.Enabled := True;
end

end;

// Valores iniciales
procedure TForm1.FormCreate(Sender: TObject);
begin
label1.Font.Color := clRed;
Label1.Caption := 'Thread Off';
end;

// Salvar los cambios registrados durante el monitoreo a un archivo de texto.
procedure TForm1.BitBtn3Click(Sender: TObject);
var
saveDialog : TSaveDialog;
StrList : TStringList;

begin

saveDialog := TSaveDialog.Create(self);
saveDialog.Title := 'Save Monitor File Changes';
saveDialog.InitialDir := ExtractFileDir(Application.ExeName);
saveDialog.Filter := 'Text file|*.txt';
saveDialog.DefaultExt := 'txt';
saveDialog.FilterIndex := 1;
saveDialog.FileName := 'Monitor_File.txt';

if saveDialog.Execute then
begin
try
if Assigned(DirectoryMonitor) then
begin
DirectoryMonitor.Terminate;
BitBtn1.Enabled := True;
end;
StrList:= TStringList.Create;
StrList.Assign(ListBox1.Items);
StrList.SaveToFile(saveDialog.FileName);
MessageDlg('El Archivo de Monitoreo Fue Salvado', mtInformation, [mbOK],0)
finally
StrList.Free
end;

end
else
MessageDlg('Cancelado el Backup del Archivo de Monitoreo', mtInformation, [mbOK],0);

saveDialog.Free;

end;

end.

El código anterior permite monitorear un archivo específico (Función ReadDirectoryChangesW) y por medio de un archivo de control determinar las líneas que se han adicionado durante el monitoreo. El código es una especialización de lo sugerido en los Msg #6 y #35.

Nota: El código anterior asume el monitoreo de archivos de texto en el cual las lineas finalizan con los caracteres de control CRLF. Solo se puede monitorear un archivo a la vez en esta versión la cual permite:

1- Iniciar el Hilo de Monitoreo a un Archivo Específico.

2- Finalizar el Hilo de Monitoreo a un Archivo Específico.

3- Salvar los cambios registrados en el Archivo Monitoreado a un archivo de texto.

4- Solo se monitorean las líneas adicionadas.

5- El archivo de control es creado cada vez que se selecciona un archivo específico para monitoreo.

El código esta disponible en el siguiente link: http://terawiki.clubdelphi.com/Delphi/Ejemplos/Win-API/?download=FileMonitor.rar

Espero sea útil :)

Nelson.

nlsgarcia
29-03-2013, 23:40:31
paquechu,

Nota: La función GetFileLastLine del código del Msg #36 se modifico para adaptarse a posibles operaciones de Delete en el archivo monitoreado por medio de la Actualización del Valor de Control (Tamaño del archivo monitoreado), como se muestra a continuación:


// Lee las n líneas adicionados a un archivo desde un valor de control de Monitoreo inicial
// El valor de control inicial es el tamaño del archivo el cual se establece al momento
// de la llamada al hilo de monitoreo.
function TDirectoryMonitor.GetFileLastLine(const FileName: string): string;
var
F1 : TFileStream;
F2 : TFileStream;
Buffer : string;
BufChar : Char;
i, pi, pf : Integer;
LastSize : LongWord;
Err : Integer;
FileControl : String;

begin
try

FileControl := ExtractFilePath(Application.ExeName) + 'FileControl.txt';

F1 := TFileStream.Create(FileControl, fmOpenReadWrite or fmShareDenyNone);
SetLength(Buffer, F1.Size);
F1.Read(Buffer[1],F1.size);
Val(Buffer, LastSize,Err);

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

pi := -1;
pf := LastSize - F2.Size;

Result := '';

// Se adicionaron n líneas al archivo monitoreado por 1 ocurrencia
if (LastSize = 0) then
begin

pf := 0;
F2.Seek(0,soFromBeginning);
while F2.Position < F2.Size do
begin
F2.Read(BufChar, 1);
Result := Result + BufChar;
end;

Buffer := Format('%.10d',[F2.Size]);
F1.Seek(0,soFromBeginning);
// Actualiza el valor de control de Monitoreo
F1.Write(Buffer[1],Length(Buffer));

end;

// Se adicionaron n líneas al archivo monitoreado por n ocurrencia
if (pf < 0) then
begin

pf := pf - 1;

repeat
F2.Seek(pi, soFromEnd);
F2.Read(BufChar, SizeOf(BufChar));
Insert(BufChar, Result, 1);
Dec(pi);
until (pi = pf);

Buffer := Format('%.10d',[F2.Size]);
F1.Seek(0,soFromBeginning);
// Actualiza el valor de control de Monitoreo
F1.Write(Buffer[1],Length(Buffer));

end;

// El tamaño del archivo monitoreado disminuyo, se ajusta el valor de control.
if (pf > 0) then
begin

Buffer := Format('%.10d',[F2.Size]);
F1.Seek(0,soFromBeginning);
// Actualiza el valor de control de Monitoreo
F1.Write(Buffer[1],Length(Buffer));

end;

finally

F1.Free;
F2.Free;

end;
end;

Esta modificación solo ajusta el valor de control en el caso de que el archivo monitoreado disminuya su tamaño, no se registra el cambio en el control TListBox.

El nuevo código modificado esta disponible en el link: http://terawiki.clubdelphi.com/Delphi/Ejemplos/Win-API/?download=FileMonitor+%28Versi%F3n+2%29.rar

Espero sea útil :)

Nelson.

paquechu
01-04-2013, 18:59:00
Hola nlsgarcia,
He probado el código y funciona a la perfección.... lee las líneas correctamente, se añadan una a una o varias de golpe.
Gran curro :-)
Muchisimas gracias por vuestra gran ayuda :-)
Un abrazo.