Board index » delphi » Multiple users updating a single Text (tstringlist) file

Multiple users updating a single Text (tstringlist) file

I want to have different users on a network be able to read and update the
same text file (virtually) simultaneously.

If I use this sequence:

  LoadFromFile('Myfile.txt');
  update the file
  SavetoFile('Myfile.txt');

it is possible that another user on the network will want to LoadFromFile
after the first user has Loaded the file, but before he has Saved the file.
It seems I want to lock other users out of the file while this update
process is occurring.

The updating process will cause multiple lines, of various lengths, to be
added to the file - often in the middle of the file.

I thought that using a file stream might be helpful, but I haven't figured
out:

  1. how to create it (what mode should be used - fmOpenReadWrite or
fmShareDenyNone??);
  2. how to have other users "twiddle their thumbs" while the first user is
updating the file. In other words, I want the second user to keep trying to
update the file until he actually does update the file.

What's the best approach? Is tfilestream even on the right track?

 

Re:Multiple users updating a single Text (tstringlist) file


fmShareExclusive    // Denies all other access
fmOpenReadWrite

Personally I would use FileOpen and a THandleStream - but that is
because in the Delphi Help it says :-

' Use of the non-native Pascal file variable handlers such as FileOpen
is not encouraged. These routines map to the Windows API functions and
return file handles, not normal Pascal file variables. These are
low-level file access routines. For normal file operations use the
normal AssignFile, Rewrite, Reset operations instead of FileOpen. '

and that incites me to use Native Windows stuff.

As for the waiting bit - dead simple :-

   If FileOpen returns a handle Less or Equal to 1 then (if the file
exists) someone else is using it - just flash up a message and wait
for one second.

Actually you would be wise to use the Block/Lock technique - rapidly
open another 'signal' file, write in your User ID and close it - then
update your real file - then Open / wipe out the ID / Close the signal
file.

That way your message can read :-

        'Data in use by [User 001] - Please Wait'

Another thing you can do is to READ the signal file to see whether you
still own it - that will allow (a supervisor) option to 'grab' the
file from another User - when and/if the User who lost the file tries
to update it they get the message :-
      'Sorry - System was Grabbed by XXXXX'

It is an unnecessary frill - but can save support calls.

On Tue, 19 Jun 2001 16:29:53 -0700, "Bob Richardson"

Quote
<b...@whidbey.com> wrote:
>I want to have different users on a network be able to read and update the
>same text file (virtually) simultaneously.

>If I use this sequence:

>  LoadFromFile('Myfile.txt');
>  update the file
>  SavetoFile('Myfile.txt');

>it is possible that another user on the network will want to LoadFromFile
>after the first user has Loaded the file, but before he has Saved the file.
>It seems I want to lock other users out of the file while this update
>process is occurring.

>The updating process will cause multiple lines, of various lengths, to be
>added to the file - often in the middle of the file.

>I thought that using a file stream might be helpful, but I haven't figured
>out:

>  1. how to create it (what mode should be used - fmOpenReadWrite or
>fmShareDenyNone??);
>  2. how to have other users "twiddle their thumbs" while the first user is
>updating the file. In other words, I want the second user to keep trying to
>update the file until he actually does update the file.

>What's the best approach? Is tfilestream even on the right track?

Re:Multiple users updating a single Text (tstringlist) file


Thanks for the great info.

Quote
"J French" <je...@iss.u-net.com> wrote in message

news:3b308b82.18124865@news.u-net.com...

Quote
> Personally I would use FileOpen and a THandleStream - but that is
> because in the Delphi Help it says :-

> ' Use of the non-native Pascal file variable handlers such as FileOpen
> is not encouraged. These routines map to the Windows API functions and
> return file handles, not normal Pascal file variables. These are
> low-level file access routines. For normal file operations use the
> normal AssignFile, Rewrite, Reset operations instead of FileOpen. '

> and that incites me to use Native Windows stuff.

I'm not sure if I'm reading sarcasm here, or not. I think you're saying
"Delphi Help says to use Pascal methods (AssignFile, Reset, etc), but I
should use the non-native FileOpen method"  And the reason is that my use
(with multiple users and file locking needs) is not a "normal file
operation", and will benefit from the low-level native Windows (i.e,
non-native Pascal) control. Did I get that right?

Re:Multiple users updating a single Text (tstringlist) file


Having multiple users updating a true "single text file" is a difficult
matter, if the file is indeed organized in this way, because a text-file
is hard to work with in-pieces.  It's an all-or-nothing block of text.

When multiple users update any file, they MUST use Windows file-locking
routines to lock the region of the file that's being updated, then make
the changes, and then unlock the region again.  (This is used by Windows
Networking as a signal that any cached copies of that file need to be
discarded, by the server and by other workstations.)  

The trouble with a text file is that it's hard to lock "only a portion of
it."  Lines of text are variable-sized.

A file consisting of fixed-length records is much easier to work with on a
concurrent basis; but then it's not a 'single text (tstringlist) file'
anymore.  Maybe it shouldn't be.

In article <tj1nkngcq0i...@corp.supernews.com>, "Bob Richardson"

Quote
<b...@whidbey.com> wrote:
> Thanks for the great info.

> "J French" <je...@iss.u-net.com> wrote in message
> news:3b308b82.18124865@news.u-net.com...
> > Personally I would use FileOpen and a THandleStream - but that is
> > because in the Delphi Help it says :-

> > ' Use of the non-native Pascal file variable handlers such as FileOpen
> > is not encouraged. These routines map to the Windows API functions and
> > return file handles, not normal Pascal file variables. These are
> > low-level file access routines. For normal file operations use the
> > normal AssignFile, Rewrite, Reset operations instead of FileOpen. '

> > and that incites me to use Native Windows stuff.

> I'm not sure if I'm reading sarcasm here, or not. I think you're saying
> "Delphi Help says to use Pascal methods (AssignFile, Reset, etc), but I
> should use the non-native FileOpen method"  And the reason is that my use
> (with multiple users and file locking needs) is not a "normal file
> operation", and will benefit from the low-level native Windows (i.e,
> non-native Pascal) control. Did I get that right?

Re:Multiple users updating a single Text (tstringlist) file


On Wed, 20 Jun 2001 10:36:42 -0700, "Bob Richardson"

Quote
<b...@whidbey.com> wrote:
>Thanks for the great info.

>"J French" <je...@iss.u-net.com> wrote in message
>news:3b308b82.18124865@news.u-net.com...
>> and that incites me to use Native Windows stuff.

>I'm not sure if I'm reading sarcasm here, or not. I think you're saying
>"Delphi Help says to use Pascal methods (AssignFile, Reset, etc), but I
>should use the non-native FileOpen method"  And the reason is that my use
>(with multiple users and file locking needs) is not a "normal file
>operation", and will benefit from the low-level native Windows (i.e,
>non-native Pascal) control. Did I get that right?

Not really - I am slightly perverse when it comes to being told what
to do.  

The native FileOpen stuff is what Windows really uses, and that what I
am used to - I like the fact that it does not raise exceptions -
rather it returns 'implied' errors.

I simply see no point in using the non-native file handling stuff
supplied by Delphi - it is disguising what is going on - and to my way
of thinking provides no benefits.

Fortunately Delphi provides both methods (native and their own) unlike
one other language vendor that I can think of.

Given that I usually 'box up' my filing operations - I prefer using my
own stuff on top of the most basic level rather than build on someone
elses 'simplifications'.

Re:Multiple users updating a single Text (tstringlist) file


Quote
> The native FileOpen stuff is what Windows really uses, and that what I
> am used to - I like the fact that it does not raise exceptions -
> rather it returns 'implied' errors.

But I'm getting an error message "Cannot open file Myfile.txt"

I run the same program twice. The purpose of the "ShowMessage" is to freeze
the program in the update mode, so that I can make sure that the second
running of the program will find AFile open. Sure enough, I get a Window's
error message.  What have I done wrong? Here's  the code.

  repeat
   STM := tFileStream.create(AFile, fmOpenReadWrite or fmShareExclusive);
   if STM.Handle <= 1 then begin
    ShowMessage('Please wait, '+AFile+' in use');  // this never appears
    STM.free;
   end;
  until STM <> nil;

  with stl do begin  // stl is a stringlist that was created earlier
   clear;
   LoadFromStream(STM);
   add(Data.text);  // simple update for testing
   STM.Position := 0;
   ShowMessage('press to update');  // freezes program
   SavetoStream(STM);
  end;
 STM.free;
 Display stl in stringgrid

Re:Multiple users updating a single Text (tstringlist) file


If you want to use TFileStream.Create then bung it in a Try/Except -
if you use the Native FileOpen then there will be no exceptions

Here is what I use for opening files - outside boxed up types:-

unit FileUtil;
   (* File Utilities - 19/2/01 JF

    *)
interface

uses
  Windows, SysUtils, Classes, Minimsg, UsLib;

Function FileLen( Fle:String ):Integer;        // Get File Length
Function OpenErr( Const Fle, OpenMode:String; Var Handle:Integer
):Boolean;
Function ErrMsg:String;
Function CopyDateErr( SrcHandle, DestHandle:Integer ):Boolean;

implementation

Function LS_GetFlag( Var Mode:String; Const Flag:String
):Boolean;Forward;

{
########################################################################

Quote
}

Function FileLen( Fle:String ):Integer;
var
  L9 : Integer;
  Handle: THandle;
  FindData: TWin32FindData;
begin
  Result := -1;
  { Check for '*' and '?' }
  For L9 := 1 To Length( Fle ) Do
      If ( Fle[L9] = '*' ) Or ( Fle[L9] = '?' ) Then
         Exit;

  Handle := FindFirstFile(PChar(Fle), FindData);
  if Handle <> INVALID_HANDLE_VALUE then
  begin
    Windows.FindClose(Handle);
    if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0
then
    begin
      Result := FindData.nFileSizeLow ;  // Note - Up to 2 Gb
      Exit;
    end;
  end;

End;

{
const fmOpenRead       = $0000;

const fmOpenWrite      = $0001;
const fmOpenReadWrite  = $0002;
const fmShareCompat    = $0000;
const fmShareExclusive = $0010;
const fmShareDenyWrite = $0020;
const fmShareDenyRead  = $0030;
const fmShareDenyNone  = $0040;

Quote
}

{
########################################################################

Quote
}

Var
FErrMsg : String ;
Function ErrMsg:String;
  Begin
  Result := FErrMsg ;
End;

{
########################################################################

  Mode : 'IORW L LR LW'  Input Output  Lock  LockRead  LockWrite /
Shared
         Note : O  Creates File and Opens in RW Mode (18/3/01 JF)

Quote
}

Function OpenErr( Const Fle, OpenMode:String; Var Handle:Integer
):Boolean;
  Var
  Mode : String ;
  RW, Share : Integer ;
  CreateFlag : Boolean ;
  ReadFlag : Boolean ;
  WriteFlag : Boolean ;
  Begin

  Result := True ;

  Mode := OpenMode ;
  CreateFlag := (InStr( Mode, 'O' ) > 0) ;  // 'O'utput

  // Default to Shared
  Share := fmShareDenyNone ;

  If LS_GetFlag( Mode, 'LR' ) Then
     Share := fmShareDenyRead ;

  If LS_GetFlag( Mode, 'LW' ) Then
     Share := fmShareDenyWrite ;

  If LS_GetFlag( Mode, 'L' ) Then
     Share := fmShareExclusive ;

  ReadFlag := False ;
  WriteFlag := False ;

  If LS_GetFlag( Mode, 'W' ) Then      // Write
     WriteFlag := True ;

  If LS_GetFlag( Mode, 'R' ) Then      // Read
     ReadFlag := True ;

  If LS_GetFlag( Mode, 'I' ) Then      // Input - Read
     ReadFlag := True ;

  If LS_GetFlag( Mode, 'O' ) Then      // Output - Write + Create
     Begin
     ReadFlag := True ;                // Best set this for Buffered
Access
     WriteFlag := True ;
  End;

  RW := -1 ;

  If ReadFlag Then
     RW := fmOpenRead ;

  If WriteFlag Then
     RW := fmOpenWrite ;

  If ReadFlag Then
     If WriteFlag Then
        RW := fmOpenReadWrite ;

  If RW = -1 Then
     MiniMessage( 'FileUtil.Open', Fle + #13 + 'Neither Read or Write
Mode Requested' ) ;

  // Create
  If CreateFlag Then
     Begin
     Handle := FileCreate( Fle ) ;
     If Handle <= 0 Then
        Begin
        FErrMsg := 'Error Creating File [' + Fle + '] ' + IntToStr(
Handle ) ;
        If FileExists( Fle ) = False Then
           FErrMsg := FErrMsg + ' - File Not Present'
        Else
           FErrMsg := FErrMsg + ' - Access Denied' ;
        Exit;
     End;
     FileClose( Handle );  // So we suceeded - close file
  End;

  // Now Open the File
  Handle := FileOpen( Fle, RW Or Share ) ;
  If Handle <= 0 Then
     Begin
     FErrMsg := 'Error Opening File [' + Fle + '] ' + IntToStr( Handle
) ;
     If FileExists( Fle ) = False Then
        FErrMsg := FErrMsg + ' - File Not Found'
     Else
        FErrMsg := FErrMsg + ' - Access Denied' ;
     Exit;
  End;

  // Good Exit
  Result := False ;

End;

{
########################################################################

  Check for Substring & Zap if Found

Quote
}

Function LS_GetFlag( Var Mode:String; Const Flag:String ):Boolean;
  Var
  Q : Integer ;
  Begin
  Result := False ;
  Q := InStr( Mode, Flag ) ;
  If Q <> 0 Then
     Begin
     MidSet( Mode, Q, StringOf( Length(Flag), Ord('x') ) ) ;
     Result := True ;
  End;
End;

{
########################################################################

  Copy File Date from one open file to another

Quote
}

Function CopyDateErr( SrcHandle, DestHandle:Integer ):Boolean ;
  Var
  Q : Integer ;
  Begin
  Result := True ;

  Q := FileGetDate( SrcHandle ) ;
  If Q = -1 Then
     Begin
     FErrMsg := 'CopyDateErr - bad Source Handle' ;
     Exit;
  End;

  If FileSetDate( DestHandle, Q ) <> 0 Then
     Begin
     FErrMsg := 'CopyDateErr - bad Source Handle' ;
     Exit;
  End;
  // Good Exit
  Result := False

End;
end.

On Thu, 21 Jun 2001 09:37:38 -0700, "Bob Richardson"

Quote
<b...@whidbey.com> wrote:
>> The native FileOpen stuff is what Windows really uses, and that what I
>> am used to - I like the fact that it does not raise exceptions -
>> rather it returns 'implied' errors.

>But I'm getting an error message "Cannot open file Myfile.txt"

>I run the same program twice. The purpose of the "ShowMessage" is to freeze
>the program in the update mode, so that I can make sure that the second
>running of the program will find AFile open. Sure enough, I get a Window's
>error message.  What have I done wrong? Here's  the code.

>  repeat
>   STM := tFileStream.create(AFile, fmOpenReadWrite or fmShareExclusive);
>   if STM.Handle <= 1 then begin
>    ShowMessage('Please wait, '+AFile+' in use');  // this never appears
>    STM.free;
>   end;
>  until STM <> nil;

>  with stl do begin  // stl is a stringlist that was created earlier
>   clear;
>   LoadFromStream(STM);
>   add(Data.text);  // simple update for testing
>   STM.Position := 0;
>   ShowMessage('press to update');  // freezes program
>   SavetoStream(STM);
>  end;
> STM.free;
> Display stl in stringgrid

Other Threads