Rebooting a Linux server unattended

 Linux  Comments Off on Rebooting a Linux server unattended
Sep 042015
 

Just so I can look it up when I need it again:

/sbin/shutdown -r now

is the command that when run as root will reboot a Linux server.

It can simply be added as a scheduled command in WebMin:
scheduledcommand

Using the %TEMP% directory

 Delphi, dzLib  Comments Off on Using the %TEMP% directory
Aug 162015
 

Sometimes you need to temporarily create a file or even a directory to put some files into. Windows (and most other operating systems, even MS DOS) reserves a special directory for this purpose and helpfully provides API methods for getting this directory (or you could just use the %TEMP% environment variable but while that is convenient for batch files, it is less convenient in a program).

So, there is the GetTempPath Windows API function. For Delphi programmers it is a bit cumbersome to use because like all API functions it works with zero terminated strings (PChar). My dzlib contains a utility function that wraps this call and returns a string:

class function TFileSystem.GetTempPath: string;
var
  Res: Integer;
  LastError: Integer;
begin
  // according to MSDN the maximum length of the TEMP path is MAX_PATH+1 (261)
  // an AnsiString / WideString always has one additional character to the length for storing
  // the terminating 0
  SetLength(Result, MAX_PATH);
  Res := Windows.GetTempPath(MAX_PATH + 1, PChar(Result));
  if Res <= 0 then begin
    // GetLastError must be called before _(), otherwise the error code gets lost
    LastError := GetLastError;
    RaiseLastOSErrorEx(LastError, _('TFileSystem.GetTempPath: %1:s (code: %0:d) calling Windows.GetTempPath'));
  end;
  SetLength(Result, Res);
end;

While it is nice to know where to put the files, sometimes you want not just a directory to put the file into but also a unique filename. Again, the Windows API can help here with the GetTempFileName function. And since it too requires you to use PChar I have wrapped it again:

class function TFileSystem.GetTempFileName(_Directory: string = ''; const _Prefix: string = 'dz'; _Unique: Word = 0): string;
var
  Res: Integer;
  LastError: Cardinal;
begin
  if _Directory = '' then
    _Directory := GetTempPath;
  SetLength(Result, MAX_PATH);
  Res := Windows.GetTempFileName(PChar(_Directory), PChar(_Prefix), _Unique, PChar(Result));
  if Res = 0 then begin
    // GetLastError must be called before _(), otherwise the error code gets lost
    LastError := GetLastError;
    RaiseLastOSErrorEx(LastError, _('TFileSystem.GetTempFilename: %1:s (Code: %0:d) calling Windows.GetTempFileName'));
  end;
  Result := PChar(Result); // remove trailing characters
end;

Sometimes you want to not only create a single file but many of them and rather than cluttering the TEMP directory with your files, you might want to create a subdirectory for this purpose. There is no Windows API for this (at least I don’t know any), so I have written my own function for it:

class function TFileSystem.CreateUniqueDirectory(_BaseDir: string = ''; const _Prefix: string = 'dz'): string;
var
  Pid: DWORD;
  Counter: Integer;
  Ok: Boolean;
  s: string;
begin
  if _BaseDir = '' then
    _BaseDir := GetTempPath;
  Pid := GetCurrentProcessId;
  s := itpd(_BaseDir) + _Prefix + '_' + IntToStr(Pid) + '_';
  Counter := 0;
  Ok := False;
  while not Ok do begin
    Result := s + IntToStr(Counter);
    Ok := Self.CreateDir(Result, ehReturnFalse);
    if not Ok then begin
      Inc(Counter);
      if Counter > 1000 then
        raise ECreateUniqueDir.CreateFmt(_('Could not find a unique directory name based on "%s"'), [Result]);
    end;
  end;
end;

This function uses the provided prefix (or the default ‘dz’), current process ID and a number (starting at 0) to create a unique directory. If creating the directory fails, the number is incremented and tried again.

Using it is pretty simple:

MyTempoaryWorkingDir := TFileSystem.CreateUniqueDirectory;

This will create the unique directory

%TEMP%\dz_815_0

Or, if you don’t want to use the TEMP directory, specify your own:

MyTempoaryWorkingDir := TFileSystem.CreateUniqueDirectory('c:\rootofallmyworkingdirs');

This will create the unique directory

c:\rootofallmyworkingdirs\dz_815_0

And if you don’t like the ‘dz’ prefix, just specify your own:

MyTempoaryWorkingDir := TFileSystem.CreateUniqueDirectory('', 'bla');

This will create the unique directory

%TEMP%\bla_815_0

Usually you want to clean up any temporary files when your program exits. In the case of a unique temporary working directory, this simply means that you delete the whole directory. Since I am lazy bastard ™ I have of course wrapped that into a helper function:

type
  IUniqueTempDir = interface ['{D9A4A428-66AE-4BBC-B1CA-22CE4DE2FACB}']
    function Path: string;
    ///<summary> Path including trailing path delimiter </summary>
    function PathBS: string;
  end;
  // [...]

type
  TUniqueTempDir = class(TInterfacedObject, IUniqueTempDir)
  private
    FPath: string;
    FDeleteOnlyIfEmpty: Boolean;
    function Path: string;
    ///<summary> Path including trailing path delimiter </summary>
    function PathBS: string;
  public
    constructor Create(const _Path: string; _DeleteOnlyIfEmpty: Boolean = False);
    destructor Destroy; override;
  end;

{ TUniqueTempDir }

constructor TUniqueTempDir.Create(const _Path: string; _DeleteOnlyIfEmpty: Boolean = False);
begin
  inherited Create;
  FPath := _Path;
  FDeleteOnlyIfEmpty := _DeleteOnlyIfEmpty;
end;

destructor TUniqueTempDir.Destroy;
begin
  // delete directory, fail silently on errors
  if FDeleteOnlyIfEmpty then
    TFileSystem.RemoveDir(FPath, False)
  else
    TFileSystem.DelDirTree(FPath, False);
  inherited;
end;

function TUniqueTempDir.Path: string;
begin
  Result := FPath;
end;

function TUniqueTempDir.PathBS: string;
begin
  Result := itpd(FPath);
end;

class function TFileSystem.CreateUniqueTempDir(_DeleteOnlyIfEmpty: Boolean = False; _Prefix: string = 'dz'): IUniqueTempDir;
var
  s: string;
begin
  s := CreateUniqueDirectory(GetTempPath, _Prefix);
  Result := TUniqueTempDir.Create(s, _DeleteOnlyIfEmpty);
end;

The function TFileSystem.CreateUniqueTempDir returns an interface which means it is reference counted. Once the interface goes out of scope, the object is freed and the destructor deletes the whole directory tree. Of course you must make sure that you keep a reference to the interface around as long as you need the files.

procedure DoSomethingTemporary;
var
  TempDir: IUniqueTempDir;
  st: TFileStream;
begin
  TempDir := TFileSystem.CreateUniqueTempDir;

  CreateAFileIn(TempDir.Path, st);
  WorkWithTheFile(st);
  DontForgetToCloseIt(st);
end; // the interface goes out of scope here

There is an overloaded function which allows to specify a different base directory as well:

    class function CreateUniqueTempDir(const _BaseDir: string; _DeleteOnlyIfEmpty: Boolean = False; _Prefix: string = 'dz'): IUniqueTempDir;

All those helper functions are in the unit u_dzFileUtils, in case you want to look it up.

dzEditorLineEndsFix 1.0.2 released

 Delphi  Comments Off on dzEditorLineEndsFix 1.0.2 released
Aug 162015
 

I just released dzEditorLineEndsFix 1.0.2 on SourceForge. It now creates its own subdirectory under TEMP where its puts the unique subdirectories for the various copies of the .ttr file rather than cluttering TEMP. It also tries to delete this directory on startup (It can’t do that on shutdown because the .ttr files cannot be deleted).

And, btw: I moved the source code back to SVN again. I grew tired of keeping my buildtools and dzlib in sync between Subversion and Mercurial and I am definitely not going to move all my projects to Mercurial or Git in the near future.

Delphi Custom Container Pack updated for Delphi XE8

 Delphi  Comments Off on Delphi Custom Container Pack updated for Delphi XE8
Jul 142015
 

I just updated the Custom Container Pack sources to support Delphi XE8. It was mostly a matter of creating the packages for the “new” version. I also added XE8 to the compilerversion.inc file.

It now compiles and installs. I have not tested it extensively.

Custom Containers Pack (CCPack) is an integrated tool and component mini-library to produce and maintain composite controls (or simply “composites”) and other containers (forms, data modules and frames). The process of building composite components looks like ActiveForm and Frame creating, but the result is the native VCL component. You can create new composites just as usual forms.

Here is the original documentation in RTF format.

It was originally developed by Sergey Orlik who posted the source code to code central

GExperts code formatter is now a little bit more Unicode aware

 Delphi, GExperts  Comments Off on GExperts code formatter is now a little bit more Unicode aware
Jul 112015
 

A new release of my experimental GExperts version is available. I fixed yet another issue with Unicode / UTF-8. It works now with some Arabic strings which got converted to question marks before.

The usual warning applies: My unit tests still work so apparently I haven’t broken too much in the process. I also tested it manually, but only with a limited set of IDEs and only some of the functionality. So you might find new bugs. If you do, please contact me through my G+ profile (see upper right).

The download links are on the Experimental GExperts page as usual.

Unicode is hard – harder than I assumed

 Delphi, GExperts  Comments Off on Unicode is hard – harder than I assumed
Jul 112015
 

In my last post I announced that the GExperts code formatter is now Unicode aware. Little did I know. Mohamed Kamel sent me a source file with Arabic strings which still got converted to question marks. So today I have dived into the source code again. I nailed the problem with Arabic strings (and added a new unit test). But I am pretty sure I missed some more.

I’ll release a new version as soon as I got feedback from Mohamed.

Anybody got some source code with Japanese and Chinese strings for me?

GExperts code formatter is now Unicode aware

 Delphi, GExperts  Comments Off on GExperts code formatter is now Unicode aware
Jul 082015
 

After getting my Delphi XE8 installation to work again I have released a new experimental GExperts version. It is the first where the code formatter should be fully Unicode aware (for Delphi IDEs supporting Unicode, that is >= 2005). My tests worked and some brave souls also tested it live.

Also, I found and fixed several other Unicode related problems in the regular GExperts code base.

In this process I also found out that most people use UTF-8 encoding for their source code and apparently nobody uses UCS-2 or UCS-4.

So, if you had any problems with the code formatter converting your special characters to question marks, you should update.

The usual warning applies: I did test it, but only with a limited set of IDEs and only some of the functionality. So you might find new bugs. If you do, please contact me through my G+ profile (see upper right).

The download links are on the Experimental GExperts page as usual.

Fixing “An Update Subscription … is required …

 Delphi  Comments Off on Fixing “An Update Subscription … is required …
Jul 082015
 

When the Delphi XE8 update1 was released it was also about the time for our company to renew my Delphi subscription. Unfortunately our accounting department fouled up paying the bill so for a short while my subscription was not valid. I knew nothing about this and installed the "Subscription Update". This left me with the following message.
An Update Subscription for Embarcadero Delphi XE8 is required which has expired prior to this release.
"An Update Subscription for Embarcadero Delphi XE8 is required which has expired prior to this release."

Not quite what you want to read when you update your development environment. The first thing I tried was uninstalling Delphi and reinstalling it from the ISO that included update 1. It didn’t help at all.

Then I got sidetracked (work always interferes with fun, doesn’t it ;-) ).

Only last Friday I came around to submitting a support ticket to Embarcadero, describing the issue. The fix turned out to be very easy:

  1. Open the licensemanager.exe from the bin directory
  2. Choose Update
  3. Restart the IDE.

It just worked.