Wednesday, December 18, 2024

The Horror of finding the right database! Part 3

If you haven't read Part 1 or Part 2...

Here is Part 1!
Here is Part 2!

There is a Window Service running that knows "everything"?

What do we need on top?


Let's rethink our idea of moving from flat files to a database. 

- Fixed Record length
- Variable record length too complicated
- SQL overhead is significant – in most cases, a simple CRUD database suffices.
- Do we really need advanced functions, views, and JOINs?
- What about transactions?

These are all good questions, but there's another crucial point that could have a significant impact and benefit!

What if we had a Windows Service that could perform advanced operations alongside communication between our client PCs? What would we do?

One part of a running System Service is: This service could install and register components without requiring administrative rights from the user. For instance, it could use regsvr32 to register COM DLLs. These COM DLLs could be compiled with the latest Delphi version and for 64-bit architecture.

This approach kills two birds with one stone.

(If you don't know me, I'm stuck on D2007 with my 12M  LOC Main Application)

I don't plan to use other compilers, but I could, for example, use C# to create a COM DLL.

Back to the database... And the question from Part 2:

"Do we still need a dedicated database server, or could we return to using a flat-file database?"

Some details for the current status:

I've been using BTREE-ISAM from TurboPower since the Turbo Pascal days, later adapting it to Delphi 3. It has worked fine without any errors since ~1998. My Application works fine with AddRec, PutRec, DelRec, and the keys to find the reference. A JOIN or anything else was never needed. This "Database" also supports variable record lengths, though I've never used that feature. But times have changed and a real database could help clean up some old methods. To be fair: Only address data is stored in this database. I have also developed a BTREE-like flat-file database that could handle any kind of data and store it compressed in one file. I use this to store large copies of text files. (No need to search). I also have a cluster-based flat file that stores compressed data but with a fixed length in a cluster. (4KB Cluster could store compressed data) benefit fixed seek positions - very fast).

I want to use a real database - and I don't talk about NOSQL - we have to use the SQL language to do our CRUD work. Perhaps we could benefit from transactions and joins and other fancy stuff. But we have the overhead of Data->SQL->DBServer and also DBServer-Text-Data. Hundreds of fields must be mapped to and from a query. Why? I have a record in memory, this must be stored and also be loaded. Why convert it to text back and forth? I don't think - no I'm sure - this could not be faster with a DB-Server of any kind. 

Nothing beats a Blockwrite.

If we have to transfer the Data over TCP/IP this is another sorry, but a binary compressed stream is also better than a huge blob of SQL Insert statements.

Perhaps the best solution for me – your application might have different requirements – is an elegant interface capable of handling any kind of record.

Since Delphi 2007 lacks comprehensive RTTI, we'll handle the RTTI operations in an XE DLL. With this information, we could create a database structure on the fly without using any SQL.

On top, we must define the indexes and for every record, we have to register a function that can get the necessary information from the record to build the keys. With all this, we could also store only these fields from a record that has a value. So we treat every field as nullable. With a really simple comparison, we could ignore all fields that have no value and just don't store or even send the data over TCP/IP. Although I haven't implemented this yet, it seems promising – at the very least faster than a SQL INSERT with numerous unused fields. (I'm talking about records that have other embedded records and a field count of 400 or more.)

I think I could write my own database server to do this job; in this case, I have no external dependencies.

Is this a good idea?

Perhaps there'll be a Part 4 – or another story about my failed attempt at developing a database. The future will prove this.

Stay tuned...


Monday, November 18, 2024

The Horror of finding the right database! Part 2

If you haven't read the first part: Here is Part 1!

Btw: This topic is not new - Here are two blog posts from 2020:

Perhaps we have special needs, but I don't think so.



The simplest database would certainly be SQLite in Journal mode. Although it should theoretically work with locking and file modes, there are many warnings against using SQLite or other file-based databases in a multi-user network environment.

So, how do we work with a server without having a real server?

Our application could communicate with other applications on the network and coordinate through a communication channel to determine which PC has access to the database at any given time. This would work, but it would be very inefficient. Each PC would need to request access, open the database, perform its task, and then close it before releasing access for the next PC.

As a proof of concept, we could write our own file to the hard drive, but this might lead to the same problems. Additionally, antivirus software tends to dislike frequent file writes in short intervals.

Encryption is also a key requirement for our database. Unfortunately, the SQLite’s encryption feature has become a real hurdle, with a $2,000/year licensing fee. So, you're either stuck with an old version or forced to pay the fee, which is not a viable option for small applications.

Another option would be Firebird. A Firebird database can be accessed either via an embedded driver or a server. The first PC that starts could access the database directly or launch the server instance locally. When a second PC starts, it could "ask" the other PCs on the network if a server instance is already running and then use that server.

We could attempt to establish a connection from each PC to the server instance on the other PCs, read a table where the active server logs its activity, and if no instance is active, we can start the server and update the table accordingly.

In theory, this should work! If the respective PC is already turned off, we would just need to wait for the connection to time out, and then start the server instance ourselves - if we are faster than another PC on the network. Another PC might have already taken over the job.

Without having programmed this functionality yet, I already foresee numerous problems with this system.

No matter how we approach it, we need communication between the PCs. Of course, we can build these functions into our application. With each application shutdown, another PC might need to "take over." For example, if I shut down my application, the communication would be interrupted, and the application on another PC would need to take control, coordinating access between PC 2 and PC 3. At this point, things start to get complicated.

Moreover, the application could crash, or the user could simply shut down the PC as mentioned above.

Starting or stopping a server instance, or applying updates, may also require administrator rights, and you wouldn’t want to constantly prompt the user for those permissions.

The conclusion of these considerations is that a Windows System Service must be installed on each PC to handle these tasks. This way, our application can simply connect to the Service-Application on the correct port, which will act as a mediator.

First, we need to develop a Windows System Service capable of maintaining the information on which PC the current server is running and how it can be reached. The service handles this task in its own thread and can respond very quickly if needed. For example, each system service receives a notification when Windows is shutting down! The service can immediately notify the other system services and, based on a priority list, instruct the next PC to start a server instance.

Within a few milliseconds, a new server will be ready to take over the work.

To establish our connection to the database, we simply need to send a query to our system service, which will return the IP and port for the server instance. Since our system service only provides the correct "login details," all database drivers work normally.

However, communication between FireDAC (for example) and the database happens without our intervention. So, if we want to execute a longer database operation, we need to inform the system service currently holding the server instance and ensure that the PC isn’t shut down until the operation is complete.

Even though communication with the database already happens via TCP/IP, FireDAC handles this communication in the background. We definitely don’t want to analyze that traffic.

A query wrapper or a wrapper for the FDConnection could handle this task.

If we’re already "announcing" the database access via TCP/IP, do we still need a real server, or can we switch back to a flat-file database?

Stay tuned for what's coming next.




Tuesday, October 15, 2024

The Horror of finding the right database! Part 1



How often do you use a database?

  • With every application?
  • With some applications?
  • Never?

A friend recently told me that he believes that if we switch our application from flat files to a database, all access would be much faster.

Of course, this is not the case.

We have large structures with many fields in records. Apart from complicated searches, there is, of course, nothing faster than writing or reading this data directly to disk with a blockwrite or reading it with a blockread command. A stream has almost the same speed apart from a few calls; both methods result in the same Windows API function.

With the overhead of passing the fields to a query so that it then generates long strings from them—which then have to be interpreted again by the database so that the data is finally stored on the hard disk with countless calls—is, of course, much slower.

To make it clear, databases have many functions that are hard to do with a flat file - especially if the file does not fit into memory.

But there is more to consider.

For a single user without any shared access, a single-file database like SQLite fits most of the needs.

If you need more, you could use MariaDB, Firebird, or any other database.

So let's start by selecting the right database! But first, what are our needs?

  • Free for use in a commercial application (no license fees)
  • Easy installation
  • Multi-users over the network
  • No dedicated server (we only have a main PC and a notebook in most cases)
  • Encryption
  • Perhaps a NAS for data storage

So, without a dedicated server, where are our data and our server instance installed? Perhaps on the main PC or on the NAS.

If the main PC is off, the notebook must fire up a server instance to load the data from the NAS (we assume the NAS is too basic to run the server).

What if the main PC gets started? Perhaps we inform the notebook to stop the server and start a server instance on the main PC—because it is faster.

How do we inform a PC in the network to stop the local server and use another one?

Every problem starts with not having a dedicated server! All file-based databases are not really "good" in a multiuser environment.

And now?

We need a CRUD database. Normally, there is no need for joins or any master-detail tables, but it would be nice to have. Do we need to access the database over SQL? Absolutely not—in most cases, we need an object-based database. So more like a NoSQL database?

How do we connect to a database? Of course, over TCP/IP.

Perhaps we could use a database, but—as always—nothing fits our needs perfectly.

Stay tuned for our ideas.

Spoiler: TCP/IP is easy to use...







Tuesday, September 3, 2024

How to execute a method with elevated rights?

 Well, let's start with the question: What are elevated rights?


We focus on Windows only for this. Elevated rights are otherwise called Windows UAC control, which is formerly known as "Admin Rights."

Sometimes you will need this to install something in Windows or get permission to change the Registry in some parts.

So how can your application get these rights? 

You can (Shell)execute your application again with "RunAs" to get these rights from the user. But in this case, you must overcome your complete startup procedure, and perhaps a window will be shown. 

At this point it is much easier to put your code into a DLL - the only thing that you need on top of that is a small application to load your DLL.

You can write this in Delphi - piece of cake. 

I like to include everything my application needs in resource so you just need to copy my exe and dll's into on your drive and execute it.

Bad news: Your virus scanners really don't like applications embedded into other applications and I would like to keep the size to a minimum. 

So my Idea was a really small application written in ASM.

How do we write applications this small these days? We ask Chat-GPT. Easy task isn't it?
The last time I wrote ASM by hand it was for the Z80 CPU... So my knowledge is a bit rusty on how to assemble and link with modern tools. And to be honest - this was an even bigger challenge than expected.

To make this story short - with a friend (he wrote his own assembler and linker) it took 2h to find the right tool... FASM every other Assembler / Links did not do the job. 

The resulting application has 2048 bytes - as a packed resource I'm down to 384 bytes... This application can load one of my DLL's execute a procedure and the Exit-Code is the result.


Tuesday, August 6, 2024

If you have an interface everything looks like a nail!

Of course: If you have a hammer, everything looks like a nail...

So the question is: What kind of implementation should I use?

Let's collect some possibilities.

  1. Interface
  2. Class
  3. Record
  4. Methods in a Unit
OK - this makes no sense... Let me try this again from another point of view. Can you guess where this will lead us again?

You have a Form and you don't want to put your business code into this form. Ring a bell?

Where to store the code? Of course in a different unit, but that is not the point.

Every form needs data, so where do we get the data from? 
We can get it from a database, but in my opinion, this is the worst way to use data-sensitive components. 

We can create a unit with procedures and functions to pass the data or get the data. or we can create a class that can hold the data as well as read and store it. 

But wasn't there a rule that a class should only do one task?

We should have separate classes or methods for saving and loading. 
So first we need a storage structure that can hold the data we need for our input on our form.

We can go the old way with new and dispose or getmem and freemen. 
We can use a record or a class. And if we need a list, we can use a TList<T> or a TArray<T>. or a linked list as in the old days? 

Are you too young to know what a linked list is? Well then, here is a short explanation:

Assume you have a record and a Pointer

PFoo = ^TFoo;
TFoo = Record
  Name : ShortString;
  Next : PFoo;
end;

Var
  Root,
  Akt,
  NewFoo : PFoo;

begin
   Root := NIL;
   New(NewFoo);
   Root := NewFoo;
   Root.Next := NIL;
   New(NewFoo);
   Root.Next := NewFoo;
   NewFoo.Next := NIL

   Akt := Roo;
   While Akt <> NIL do
     begin
      Writeln(Akt^.Name);
       Akt := Akt^.Next;
     end;
end;
 

You get the memory with New and you can easily iterate through the list. You have to free the memory with dispose.

Is it good or bad?

On the positive side: You do not need a contiguous memory block to store the data. Unfortunately, you do not have direct access to the nth element.

You can of course create the record with New and store the pointer in an array. This means you can access the nth element at any time. You only need a memoryblock for the size of n-pointer, but even in this case you have to release all elements with dispose. Let's ignore the smart records at this point.

A TList<TFoo> and a TArray<TFoo> again must fit a linear block of memory.

You can use a TObjectList with owns value to great your classes get destroyed.

From all this I always come back to: TArray<IFoo>.

Why no link to the Implementation just to the Interface declaration and no problems with memory.

So happy coding...




Wednesday, September 27, 2023

Meine Gründe doch zu den Forentage zu fahren!

Moin Zusammen!

Sorry, my non-German readers - this is a post only for the German community.

Habt Ihr nicht alle nach den ForenTagen „geschrien“? Warum sind den die Anmeldungszahlen nicht schon durch die Decke?


Mist – hab mich selber noch nicht angemeldet… 
Direkt mal nachholen.

Uwe hat ja schon das ein oder andere geschrieben – Thema Agenda...

Sorry - ich habe leider auch keine Zeit einen Vortrag vorzubereiten. Aber natürlich stehe ich für Fragen zur Verfügung.

Ist es nicht genau das, worum es bei den Forentage geht/gehen sollte – besonders nach der C-Zeit – IRL-Treffen um sich auszutauschen und Kontakte zu knüpfen?

Hier meine Gründe warum ich zu den Forentage fahre:
  1. Ian Barker treffen – Er kommt extra nach Deutschland. Ich Skype zwar öfter mit Ihm am persönlich ist doch immer etwas anderes…
  2. Neuste Katzen Infos mit Mathias austauschen.
  3. Der Community-Abend
  4. Leute Treffen, die mich davon überzeugen wollen, dass ich es sowieso falsch mache. (Natürlich nur um die Argumente zu entkräften)
  5. Fragen zu beantworten.
  6. Die Delphi-Entwickler „Jugend“ zu fördern. (Neugierig was das bedeutet? Dann meldet Euch an und Fragt mich!)
  7. Ich bin gespannt, ob Mathias wieder die Übersetzung macht – wie in den guten alten Zeiten für David-I.

Habe ich etwas vergessen?

Ach ja die Vorträge.

Delphi 12 – sicherlich wieder ein großer Schritt nach vorne – kenn ich schon, aber solltet Ihr euch ansehen.
Den Vortrag von Thomas kenne ich auch schon, da er in unserer Delphi-Frühstücks-Crew ist und uns das schon mal vorgestellt hat – trotzdem Interessant.
Ich mag keine „Webanwendungen“ und auch nicht den Trend dahin – Sorry Michale, aber ich denke es könnte für viele Projekte interessant sein.
Naja – die Weiterentwicklung von TMS Web Core ist sicherlich beeindruckend. Aber wie Ihr ja sicherlich wisst, erzeuge ich lieber ISAPI.DLL’s – trotzdem werde ich mir den Vortag ansehen.
Na gut „stochastische Bestandsprojektionen“ – das habe ich wohl in Mathe verschlafen – oder war in der Zeit im Computerraum.

Also das sind meine Gründe…

Wenn Ihr noch einen Braucht, ja ich beantworte auch Fragen zu meine FDK und zu meinem MVVM-Framework…

Ich hoffe wir sehen uns im Shamrock…

Mavarik 

PS.: Ihr kennt Ian Barker nicht?

Dann habt Ihr sicherlich die Live-Streams der Apocalype Coding Group während Corona verpasst: Hier ist der 32h Replay!

oder

diese Ankündigung!

Friday, September 1, 2023

Escape the Button-Click development. Part II

 I know it's quite some time since my last blog post -  Sorry I was too busy...

if you have not read the 1st part, here is the link:
Escape the Button-Click development. Part I

OK - now the next question is how to update the Form/View.

We have to decide if we want to link the View to the ViewModel. Let's take a closer look at the differences.

Link to the View

In this case, you can write something like fView.Grid.Cells[0,0] := 'Date/Time';

Not bad - only some lines must be changed to address the View-Reference, but on the other hand, you also have the link to the view in your unit tests, and that is something we want to avoid. So this is a shortcut that only helps with separating your code from the View, but not a step to be able to create nice unit tests.

Not link to the View

This is a good approach, but how to update the view?
Without any special components at your Viewmodel or special Components on your View you have to call some kind of property change procedure by hand.

If some content has changed in your Viewmodel e.G. 

fName := 'Frank';
PropertyChange;

On your View there must be at least some code to handle this like:

Procedure PropertyChange;
begin
    EdName.Text := fViewModel.Name;
end;

Of course, you do not want to create a procedure for each control/field and you also don't want to update every UI-Element on every change of one field.

You can use a Enum:

Type
  ToUpdate = (toAll,tuName,tuTown...);


But in this case, I would simply use an Integer - perhaps you define a const value for each, but this is up to you.

This is your View:

  TMainView = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Save: TButton;
  private
    fViewModel : TMainViewModel;

    Procedure PropertyChange(aWhat : Integer);
  public
    Constructor Create(aViewModel : TMainViewModel);
  end;

And this is the PropertyChange:

Procedure TMainView.PropertyChange(aWhat : Integer);
begin
  case aWhat of
    0 : begin
          Edit1.Text   := ''
          Edit2.Text   := ''
          Save.Enabled := false
        end;
    1 : Edit1.Text   := fViewModel.Name;
    2 : Edit2.Text   := fViewModel.Street;
    3 : Save.Enabled := fViewModel.CanSave;
  end; // of case
end;

That's it - not very fancy, but this approach is doing the job. If you want to write a unit test for this you can just write a little mockup for this procedure and you can test the behavior of your "Save-Button".

That is all for the moment.

Wednesday, November 16, 2022

Escape the Button-Click development. Part I

Over decades one major selling point for Delphi was the easy development pattern of...
Place a button on the form, click it, and put the source inside the auto-created onClick procedure.

If you have never programmed like this throw the first stone.

Is it bad?

Who am I to criticize generations of developers who have created countless products and built their businesses this way?

If you do not want to create unit tests - this way of development is probably not a problem. Perhaps your project was growing over time, I bet there was a point in time when you recognized that your source code reaches a state where it was barely maintainable. You're afraid to change something because there's a big possibility that something else will break with that change. Am I right?

I also bet you have heard about unit testing, but you were unable to adapt these simple unit test examples to your project and that's why you don't "believe" in unit testing at all...

Still not interested in unit testing?
You don't want to refactor a single line of code in your project?

ok, in this case, you can stop reading... Have a nice day.

Since you're still reading on...

Part I


Let's talk about:

- Dependency Injection
- Composition (Root)

I do not talk about MVVM  - this time -, because converting a legacy project to MVVM is not an easy task by changing a few lines of code.

So what can we do to make a first and easy step? Perhaps your First idea is to separate code from your forms... Also not so easy, because the code is full of links into form components and I bet you hold all your data in visual components. Especially if you don't use DB or other data-aware components on your form.

So lets start simple:

Take one form and create a new unit with a comparing name... Like customers.pas/dfm -> customers.handler.pas. You can call it customers.controller.pas or even customers.viewmodel.pas.

For every onClick, onDBLClick and so on you create a procedure in the new unit. As we are not in the strict MVVM envirement this time you can reference the form instance in the new unit. After some copy and paste you can delete some uses in your form unit. (This was the stupid part)

Your application should work the same ways as before.

Next step is to create a class in your new unit an put all your procedures into this class. (of course you can skip the first step an create the class directly). Now we need "somebody" to create and hold this class.

Your form could do this job or you create a Unit Compositon.pas where you put all your form and viewmodel creation.

Then you only need to link this unit to create every other unit.
Like TCompositon.CustomerView.ShowModal.

Type
  TComposition = Class abstract
    public
      Class function CustomerView : TForm;
  end;

Class function TComposition.CustomerView : TForm;
begin
  Result := TCustomerView.Create(Application);
  Result.ViewModel := TCustomerViewModel.Create;
end;

If you need any business logic in your Customer.ViewModel you could also use a dependency injection for this or you use a global service-locator...

But this part we will see in part two.

Have a nice day.









Sunday, May 22, 2022

How to format your Delphi-Sourcecode!

At first:

Formatting your source code the Embarcadero way is not a mistake or to make it clear, probably the best way to format your code. Especially if you want to share your code with other developers.

But...This is not my way...

So I'm doing it wrong? No... I have good reasons to format my source code in a different way!



I have implemented some of my formatting rules many many years ago and some of them in the last 5 years. Some rules I developed at a time when nobody talked about that a procedure should have only 75 lines or "must" always fit completely on the screen. Therefore it was necessary to guess from the formatting what is "hidden" in the invisible part.

There are four kinds of rules:

  1. Formatting and Indenting
  2. Formatting on Syntax
  3. Naming
  4. Empty lines or other "small things"

All rules are "just" to make the code more readable or in some cases better maintainable.

One drawback of my rules: No formatter is able to format Delphi source code 100% according to my rules.

Therefore I started the development of my own code formatter some time ago. My formatter is using a different approach to format code. First, a tokenizer creates the syntax tree, and then a procedure is called for each part.

E.g. to format the uses list, a procedure "format_Uses" with all the necessary sources is called. By default, the code just formats it the Embarcadero way or you could implement your own method for this. So beyond some settings, you can do everything!

I'm very busy with my main "job" for some time and that's the reason this project is in the WIP folder.

So enough talk, here are my rules.

Let's start with a simple one... And don't expect a complete list here it would be out of scope for a little blog post. If you like my style of formatting or my rules, please write a comment. If enough developers would like to see more, perhaps I consider writing a complete rule book.

case Whatever of
  whNone : ;
end; // of case

All case ends gets this comment because this end is the only end without a begin.

TFoo = class   
  private
   
fWhatever : String;
   
fCount    : Integer; 

    function 
GetWhatever : String;
    procedure
SetWhatever( Const aValue : String );
    function  GetCount : Integer;
    procedure SetCount( aValue : Integer );
  public
    Constructor Create;
    Destructor  Destroy;override;

    Property Whatever : String  read GetWhatever write SetWhatever;
    Property Count    : Integer read GetCount    write SetCount;
end;

OK, this end has also not a begin, but the is not inside the source code where you have multiple levels of begin ends. For many years I've written FWhatever, but a lower f is more readable in many cases, like FField or fField. The lower "f" is easier to ignore while reading. Also, every function gets an extra space so that the method names are in the same column. the sane for the destructor. There is an empty line after the vars... And the properties got formatted by length for better readability.

Please compare this with:

TFoo = class   
private
  
FWhatever:string;
  
FCount:Integer; 
  function
 GetWhatever:String;
  procedure 
SetWhatever(const AValue:string);
  function GetCount:Integer;
  procedure SetCount(AValue:Integer);
public
  Constructor Create;
  Destructor Destroy;override;
  Property Whatever:String read GetWhatever write setWhatever;
  Property Count:integer read GetCount write SetCount;
end;

Well-formatted source code becomes more and more important for me the older I get.

Naming

Again field values get a lower "f", params get a lower "a", local vars in methods get a lower "l", and const values get a lower "c". I know the small l is not so easy to distinguish from the upper "I", for Interfaces.

But you would never write IFoo := NIL... 

User   
 
System.SysUtils
, System.Classes
, FMX.Graphics
, Web.HTTPApp
, FireDAC.Phys
, FireDAC.Phys.MySQL
// , FireDAC.Phys.SQLite
, Delphiprofi.FDK.FireDAC
, Delphiprofi.FDK.QRCode
, Delphiprofi.FDK.Server.ISAPIHandler
{$IFDEF DEBUG}
, Delphiprofi.FDK.Logging
{$ENDIF}
, Settings
, HTMLHandler
;

By formatting the uses with a leading "," and only one unit for each line, you can easily comment out some units and excluded unis by IFDEF is much better readable. After that, I like to sort my units..

  1. System
  2. Plattform
  3. Other RTL
  4. Frameworks like my FDK
  5. Units from the project. 

Please compare this with:

User   
  
Settings, Web.HTTPApp, System.SysUtils, Delphiprofi.FDK.FireDAC,
FMX.Graphics, {FireDAC.Phys.SQLite}, Delphiprofi.FDK.Server.ISAPIHandler, FireDAC.Phys, FireDAC.Phys.MySQL, System.Classes, Delphiprofi.FDK.FireDAC, Delphiprofi.FDK.QRCode, Delphiprofi.FDK.Server.ISAPIHandler{$IFDEF DEBUG}, Delphiprofi.FDK.Logging{$ENDIF}, HTMLHandler;

Formatter on Syntax

Do you remember these days, when your source looked like this:?



These days I created a simple rule...

Do you know, if the "if FileExists ..." has an else part? No, not from this point in the source code. Then you have to scroll down and if the procedure is very long, you have to scroll way too far down for this information.
If the "if" has an else part, the "then" is in the next line, if not the then is in the same line as the "if".

So simple, this if has an else:

if FileExists(fLogFilename) 
  then begin
         FS := TFileStream.Create(fLogFileName, fmOpenReadWrite);
...

This if has no else:

if FileExists(lLogFilename) then
  begin

    FS := TFileStream.Create(lLogFileName, fmOpenReadWrite);
...

Of course, never format it this way, because the lines from begin to end do not work:

if FileExists(LogFilename) then begin
  FS := TFileStream.Create(LogFileName, fmOpenReadWrite);
...

So it look like this if you have only one line:

if 
FileExists(aLogFilename)
  then FS := TFileStream.Create(aLogFileName, fmOpenReadWrite)
  else FS := TFileStream.Create(aLogFileName, fmCreate);

In any other cases, it looks like this:

if FileExists(cLogFilename)
  then begin
         FS := TFileStream.Create(cLogFileName, fmOpenReadWrite);
         Whatever := 'XX';
       end
  else begin
         FS := TFileStream.Create(cLogFileName, fmCreate);
         Whatever := 'YY';
       end;


and by the way... cLogFilename a const not a var anymore. And if you look up you can recognize one var is a (l) local, one is part of an object (f), and of is a param to this method (a) containing this code... If you just write LogFilename like in the bad example - you have no clue where the var is defined.

Empty lines and CR's


Where to put an empty line an where not, is the most overseen method to make your code more readable. Let's take a look at this bad example (stupid code):

Procedure TMainModel.LogVars(LogFilename:string);
var i:Integer;FS:TFilestream;
begin
 
LogFilename:=TPath.Combine(Path,Logfilename);startconvert:=true;
  if FileExists(LogFilename) then begin
    FS := TFileStream.Create(LogFileName, fmOpenReadWrite);
  end else FS := TFileStream.Create(cLogFileName, fmCreate);
  for i:=0 to varlist.count.1 do begin
    for var k:=0 to varlist.count-1 do varlist[k] := prepare(varlist[k]);
    
fStartConvert := true;
    if varlist[i].MustConvert then convert(varlist[i]);
    Case varlist[i].Kind of
      tkStrLog(FS,varlist[i].ForLog);
      tkInt : 
Log(FS,varlist[i].AsString);
    end;
    if varlist[i].MustConvert then reconvert(varlist[i]);
  end;
  startconvert:=false;
end;


{ -------------------------------------------------------------------------
  (c) by Peter Parker
  1998 Version 1.0 of Whatever...
  Procedure to display a message 
  ------------------------------------------------------------------------- }

Procedure TMainModel.Whatever(Display:string);
begin
  if Display.trim<>'' then MyMessage(Display) else
    raise Exception.Create('no Text to Display')
  if Display='-' then Memo1.Lines.Clear;
end;

Ok, now use my rules... before every "for" there is an empty line, also after the "for". The same rule applies to "if" and case, but not if before is a "begin" or "try" or after is an end ( of course ). Only one empty line between methods. Never write code behind an "elseless" then. (if you read the Display trim stuff... at the first millisecond it looks like this "if" raises the exception.  Here is my code (and simple i is kept, and no stupid comments) :


Procedure TMainModel.LogVars( Const aLogFilename : String );
Var 
  i            : Integer;
  lFS          : TFilestream;
  lLogFilename : String;
begin
  
lLogFilename  := TPath.Combine(cPath, aLogfilename);

  if FileExists(lLogFilename) 
    then lFS := TFileStream.Create(cLogFileName, fmOpenReadWrite)
    else lFS := TFileStream.Create(cLogFileName, fmCreate);

  try
    for var k := 0 to varlist.count - 1 do
      
varlist[k] := prepare(varlist[k]);
    
    for
 i := 0 to varlist.count - 1 do
      begin
        fStartConvert := true;

        if varlist[i].MustConvert then 
          convert(varlist[i]);

        Case varlist[i].Kind of
          tkStr : Log(FS,varlist[i].ForLog);
          tkInt : 
Log(FS,varlist[i].AsString);
          
{$IFDEF DEBUG}
          else raise DeveloperException.Create('you forgot a case entry for .Kind');
          {$ENDIF}
        end; // of case
  
        if varlist[i].MustConvert then 
          reconvert(varlist[i]);
      end;
  finally
    lFS.Free;
  end;

  if fStartConvert then
    fStartConvert := false;
end;

Procedure TMainModel.Whatever(Display:String);
begin
  if Display.trim = '' then
    raise Exception.Create('no Text to Display'); 

  MyMessage(Display);
end;

Every case that has a limited set will raise an exception so you "never" forget to update your cases. If possible Early exit a procedure - this rule for (exit and exceptions). Strings as params get a "Const"... Sometimes you need a local var because of "const", but better if you copy it to a local instance at the very end than passing strings around without a "const". (Consider the string has passed around more than one method.

Now I hope you have an idea why I do it my way. If you consider that my rules are something you would like to use in your code... Be my guest and please leave a comment.

Monday, March 21, 2022

What is the easiest way to create a dynamic web page?

Before I will talk about the topic: Yes, my #DMVVM project is still on hold. I was just too busy to find the time to do the last steps.



I do not talk about the HTML part... From my point of view, it has to be Bootstrap or something similar, so that every target device could be used.

Of course, I'm not talking about static HTML pages, and if you know me just a little bit, you know that I hate PHP and wouldn't use it.

I haven't tested the cross-compiler for Pascal to javascript yet, so even though I've seen it before, I can't really make a judgment on it
.
Of course, I have been using the Webbroker functionality for my WEB projects for years. This technology is ideal for dynamic websites. 
In the past, I had also set up projects with ASP.NET - actually the absolute best way to execute server-side code. Unfortunately, the last compiler (except Prism) was Delphi 2007 and nowadays I would like to use features of the current Delphi versions and compile my ISAPI.DLL to 64 bit.

One really good feature of the Webbroker is, that you can also create it for Linux, but in my case, I always use a Windows-Server. So while the ISAPI.DLL is loaded only once into the memory space of the IIS. The execution performance is the best you can get. Native code running directly on the CPU to deliver the content. 

So why not use it and be happy?

You can upload an HTML file to the Server, you can change tokens to any value you like, you can use a table-producer to show a complete database table inside your content...

But there is one drawback: For every change, you have to recompile your DLL and upload it to the server.
This is of course the same or even more painful if you're not the web designer.

While searching the internet for ideas I found a Razor implementation from Marco Cantù. A nice little piece of code to integrate some functionalities to HTML. But after one day of rewriting, I stop the development and forgot about this idea.

One year later I was working on a new idea to upgrade my TWebbrowser interface to a new level. In our main application, we are using the TWebbrowser component to autofill forms of many different websites. 

(Sad story, which would lead too far here, why the various websites are not able to provide a simple REST web interface)

Some of these websites are disabling the IE so I must upgrade to EDGE. 

With this interface I have reached the same point as with the web design: For every change, I have to recompile the main program.

End of the story? 

Of course not. I had worked briefly on a project where I wrote a UI test framework for our software. For this, I had to use the Pascal script from RemObjects. A small IDE was quickly put together with these components. Unfortunately, the component is somewhat "unwieldy" and I hate to install packages into my IDE. On top, I do not like to click invisible components on a form.

Therefore I wrote - as always - an interface wrapper for these components...

An IDE interface with compiler and runtime and of course separate compiler and runtime.

To do it right from the start, I created the wrapper directly in Fluentdesign and included it in my FDK right away.

Using this Wrapper for webform autofill was done in minutes and now I'm able to load the right methods to do the autofill from our website, without installing a software update of our main application.

Maybe you already have an idea what this is all about.

Of course, now I use this interface for my web design.

Using Pascalscript for web pages is not new, but I have different requirements.
  1. A web designer must be able to modify the code.
  2. I want the bootstrap layout of the whole website to be visible in the editor so that you don't lose the WYSIWYG view.
  3. I want to have a template system, so i don't have to copy header, footer, navigation, etc. in every HTML file.
  4. I want to be able to write inline Delphi (PascalScript).
  5. I don't want to have to parse the Delphi part over and over again.
And already the solution was obvious:

I parse the HTML page, find includes, find the Delphi parts and find tokens that have to be replaced.

So when a new or changed web page is uploaded to the server, I compare the creation date of the file with the compiled version. If the date differs, the page is compiled and written to disk as a new file, with a replacement table and the appropriate compiled Delphi parts.

If the date ~ is the same, the file is loaded and a new response stream is created from the static parts, the replacements, and by executing the Delphi parts. Vola.

Even though in my tests the analysis and compilation of a "normal" index.html take less than one millisecond, the execution is of course only a stream copy and the runtime of the Delphi scripts.

Usually also faster than one millisecond. (Of course not, if you read 10 million data sets from a database) Here the execution duration is naturally still affected by the Delphi parts.

At the moment I have no time for a demonstration, but a Webinar is on my to-do list.

So stay tuned...