Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Varios (https://www.clubdelphi.com/foros/forumdisplay.php?f=11)
-   -   Tail -f en delphi (https://www.clubdelphi.com/foros/showthread.php?t=82595)

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.

Cita:

Empezado por Julián (Mensaje 457563)
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

Cita:

Empezado por paquechu (Mensaje 457564)
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

Cita:

Empezado por Julián (Mensaje 457592)
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

Cita:

Empezado por paquechu (Mensaje 457593)
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

Cita:

Empezado por paquechu (Mensaje 457558)
...
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.
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.

Cita:

Empezado por nlsgarcia (Mensaje 457541)
paquechu,


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


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

Cita:

Empezado por paquechu (Mensaje 457609)
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).

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. :-)

Cita:

Empezado por ecfisa (Mensaje 457610)
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).

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

Cita:

Empezado por paquechu (Mensaje 457611)
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

Cita:

Empezado por paquechu (Mensaje 457612)
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

1 Archivos Adjunto(s)
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,

Cita:

Empezado por 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:
Código Delphi [-]
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/Delph...oryMonitor.rar

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

Revisa estos links:
Cita:

1- ReadDirectoryChangesW function : http://msdn.microsoft.com/en-us/libr...=vs.85%29.aspx

2- WaitForMultipleObjects function : http://msdn.microsoft.com/en-us/libr...=vs.85%29.aspx

3- CreateFile function : http://msdn.microsoft.com/en-us/libr...=vs.85%29.aspx
Espero sea útil :)

Nelson.

nlsgarcia 29-03-2013 22:48:29

paquechu,

Cita:

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

Cita:

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

Cita:

Empezado por paquechu
...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:
Código Delphi [-]
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/Delph...ileMonitor.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:

Código Delphi [-]
// 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/Delph...i%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.


La franja horaria es GMT +2. Ahora son las 12:59:51.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi