Reading Bookmark Library System data files

bookmark library system logoThe school I work for has a library system called Bookmark. At the start of the year, just before the students come back, I help the librarian update all the student details in the system. I do this by exporting out the details from SAS2000 and importing them into Bookmark. Of course, as it only occurs every 12 months, we forget how to do the process each time and have to find the documentation how to do it each time. Also throughout the year, new students come and others leave. My goal was to make this all easier to handle and make it so the student details would already be in the library system before they first went to borrow a book. What follows is part of my effort to make it easier to keep the library system up to date.

Bookmark Library System

Reverse Engineering the File Format

Bookmark uses its own custom binary file format. It stores various details in different files making a kind of relational database. To help reverse engineer the file format, I used a hex editor. The borrower names are stored in a file called BORROWER.DAT. Some extra info about the borrower is stored in a file called BORRCMTS.DAT. In the hex editor, it shows the location of different pieces of data in the file. From this I was able to determine that each borrower had a record of 256 bytes, with a 256 byte header at the start of the file. Also for borrowers that have been deleted the relevant 256 byte section is simply zeroed out. The next step was to split each record up into the fields.

  TLibraryStud = record
    Name     : array[0..26] of AnsiChar;
    XX       : array[0..1] of AnsiChar;
    Group    : array[0..8] of AnsiChar;
    MaxCount : BYTE;
    xxx      : BYTE;
    Addr     : array[0..29] of AnsiChar;
    City     : array[0..19] of AnsiChar;
    postcode : array[0..5] of AnsiChar;
    Phone    : array[0..11] of AnsiChar;
    Group2   : array[0..1] of AnsiChar;
    Gender   : ansichar;
  end;

After a bit of guessing and trail and error I came up with the record above to hold the data.

Saving Borrower Details to Database

First I created a table called bookmark in a database in SQL Server. This table stored the same info as what was in the bookmark data file. Because the record id’s don’t change, I am able to simply delete all the data in the bookmark table and reload it from the bookmark files. I do this in a transaction, so that if anythings fails, I am able to rollback to the last complete successful import.
Below is the final code to make it work.

procedure TDataModule.LoadBorrowers(path:String);
var
  FromFStream : TFileStream;
  FromF2Stream: TFileStream;
  NumRead,numRead2, NumWritten: Integer;
  str : AnsiString;
  stud : TLibraryStud;
  studCom : TLibraryStCm;
  filename,filename2 : String;
  currentPos : Integer;
  lastPos : Integer;
  i : Integer;
begin
  filename  := IncludeTrailingPathDelimiter(path) +'BORROWER.DAT';
  filename2 := IncludeTrailingPathDelimiter(path) +'BORRCMTS.DAT';

  if(not (FileExists(filename) and FileExists(filename2))) then
    begin
      Exit;
    end;
    FromFStream := TFileStream.Create(filename,fmOpenRead or fmShareDenyNone);
    FromF2Stream := TFileStream.Create(filename2,fmOpenRead or fmShareDenyNone);
  begin
    begin
        lastPos := 0;
    i := 0;

      ADOConnection1.BeginTrans;
      ADOQuery1.SQL.Text := 'DELETE FROM Bookmark;';
      ADOQuery1.ExecSQL;
      ADOQuery1.SQL.Text := 'INSERT INTO Bookmark (tid,Name,Group1,Addr,City,Postcode,Phone,Group2,Gender,AdminID,maxCount) VALUES(:ID,:name,:group1,:addr,:city,:postcode,:phone,:group2,:gender,:adminID,:maxCount);';

    try
      repeat
        FromFStream.ReadBuffer(stud,128);
        FromF2Stream.ReadBuffer(studCom,64);
        if((length(Trim(stud.NAME))<>0) and (i>0)) then
         begin
          ADOQuery1.Parameters.ParamByName('ID').Value := i;
          ADOQuery1.Parameters.ParamByName('name').Value := Trim(stud.NAME);
          ADOQuery1.Parameters.ParamByName('group1').Value := StripNonAscii(Trim(stud.Group));
          ADOQuery1.Parameters.ParamByName('addr').Value := Trim(stud.Addr);
          ADOQuery1.Parameters.ParamByName('city').Value := Trim(stud.City);
          ADOQuery1.Parameters.ParamByName('postcode').Value := Trim(stud.postcode);
          ADOQuery1.Parameters.ParamByName('phone').Value := Trim(stud.Phone);
          ADOQuery1.Parameters.ParamByName('gender').Value := Trim(stud.Gender);
          ADOQuery1.Parameters.ParamByName('group2').Value := Trim(stud.Group2);
          ADOQuery1.Parameters.ParamByName('adminID').Value := StringReplace(Trim(studcom.barcode), '-', '/', [rfReplaceAll, rfIgnoreCase]);
          ADOQuery1.Parameters.ParamByName('maxCount').Value := stud.MaxCount;
          ADOQuery1.ExecSQL; 
         end;
        Inc(i);
      until (FromFStream.Position-FromFStream.Size=0);
    finally
      FreeAndNil(FromFStream);
      FreeAndNil(FromF2Stream);
      ADOConnection1.CommitTrans;
    end;
    end;
  end;
end;

Automating Import

To ensure the data is always up to date in the SQL Server database, as part of the backup process each night, this program is run with Windows Task Scheduler.

Going further

Bookmark has more files that can be imported in a similar way. Files for book records, loan data and book reviews are all there.

Working with 64-bit Windows Applications

Microsoft Windows has had a 64-bit version since they first brought out a 64-bit version of Windows XP. The 64-bit version of Windows XP was not well supported by many programs, but started the progress of conversion of Windows to 64-bit. One of the main things that 64-bit enables is addressing greater than 4Gb of memory. In the last year or two, it is finally getting to the point where low-end machines come with more than 4Gb of memory. 64-bit also helps with memory layout randomization. 64-bit increases the number of possible locations that memory can be allocated to, making it harder to complete a successful buffer overflow attack. The other thing that 64-bit version allows is various new CPU instructions. These can help improve performance for various tasks.
64-bit apps from task manager

64-bit drawbacks

One of the drawbacks is that you need 64-bit drivers for all your hardware. At this point though, this is unlikely to be an issue as all new hardware will come with 64-bit drivers. For very old hardware though, there may be no 64-bit driver and make it so the old piece of hardware has to be replaced. 64-bit apps also generally require more memory. This is because to store a memory address you need to use a 64-bit pointer instead of a 32-bit pointer. Finally, although 64-bit versions of Windows support running 32-bit programs, some programs that support plugins need to have plugins that are the same as the parent app.

64-bit Windows Programs

Waterfox
Waterfox is a 64-bit version of Firefox. My usage pattern definitely benefits from the 64-bit version. I quite often have 150+ Tabs open and have seen memory usage up to 6.5Gb.
7-zip
7-Zip has a 64-bit version of the program. This would benefit from the new 64-bit instructions and the ability to more easily handle larger than 2Gb zip files.
Microsoft Office
I have been running the 64-bit version of Microsoft office since 2010. Compatibility with other programs can be a big issue with office, as the programs need to be 64-bit as well. This can be an issue for many older programs that have not been updated for a long time. Even many current versions of programs still don’t have 64-bit versions. The only way to deal with that is to remove the 64-bit version of Office and install the 32-bit version of Microsoft Office.
The biggest issues are programs that use Microsoft Word for Mail Merging, Outlook for setting emails and programs that need to be able to read Microsoft Access .accdb files. One of the issues that the 64-bit office version has caused me is that it makes it very difficult to develop in Delphi and use the latest the .accdb file format. This is because the Delphi IDE is 32-bit and the driver to read the .accdb file is 64-bit. This means that while you can develop a 64-bit version of an app that uses the driver, you can use all the RAD environment in Delphi.