2006. szeptember 9., szombat

Reporting file size in a descriptive string


Problem/Question/Abstract:

How to tell the file size in a descriptive string like "2.33MB", or "233 bytes", as Windows Explorer does in a status bar?

Answer:

The articles of Adam Lanzafame and NYB about file size string, and follow-up discussions inspired me to come with a solution which would resolve some of the issues of both aforementioned approaches/implementations (see the DPFileSize unit attached below).

Some of these issues are:

Adam's function depends on the external library presence, specifically SHLWAPI.DLL (Shell Light-weight Utility Library), which is essentially a helper library and may not be installed on all systems.

For files from 1000 to 1023 bytes in size both Adam's and NYB's functions return the size in bytes, while Windows Explorer displays '0.99KB'

Windows Explorer doesn't add space between a number and 'KB', 'MB', and 'GB', while both Adam's and NYB's functions do.

NYB's function always displays two digits after a decimal point (even for bytes), while the main idea of how Windows Explorer represents the number here is obviously to keep three significant digits, including leading zero before decimal point (see item 2 above)  

NYB's function rounds the resulting value to the nearest value of the least significant digit. The Windows Explorer approach seems to me as more consistent - we may accept either one of the rounding directions, but it is better to be fixed. The direction used by Explorer is towards lesser value of the least significant digit, so it consistently shows that a file is at least of indicated size.

All these issues have been addressed in the unit below.

//******************************************************************************
//
// Unit Name: DPFileSize
// Purpose  : Functions for reporting file size with a descriptive string
// Author   : (c) 2001 Dmitri Papichev {Dmitri.Papichev@iname.com}
// Comments : Specially for www.delphi3000.com
//
//******************************************************************************

unit DPFileSize;

{==============================================================================}
interface

const
  KB = 1024;
  MB = KB * KB;
  GB = MB * KB;

  {main function}
function GetFileSizeString(const AFileName: string): string;

{helper functions, surfaced here as they might be used on their own}
function DPGetFileSize(const AFileName: string): integer;
function GetSignificantDigits(const ARealNumber: double;
  const ADigits: integer): string;
function FormatFileSizeValue(const AValue: integer): string;

{==============================================================================}
implementation
uses
  SysUtils,
  Classes;

{------------------------------------------------------------------------------}
{returns the string representing the file size for a given filename, in a way
similar to what Windows Explorer does}

function GetFileSizeString(const AFileName: string): string;
begin
  try
    Result := FormatFileSizeValue(DPGetFileSize(AFileName));
  except
    on E: Exception do
    begin
      Result := E.Message;
    end; {on}
  end; {if}
end; {--GetFileSizeString--}

{------------------------------------------------------------------------------}
{returns file size in bytes for a given filename}

function DPGetFileSize(const AFileName: string): integer;
var
  AFileStream: TFileStream;
begin
  AFileStream := TFileStream.Create(AFileName,
    fmShareCompat or fmShareDenyNone);
  try
    Result := AFileStream.Size;
  finally
    AFileStream.Free;
  end; {try}
end; {--GetFileSizeDP--}

{------------------------------------------------------------------------------}
{returns first ADigits significant digits of ARealNumber,
with a decimal point if any}

function GetSignificantDigits(const ARealNumber: double;
  const ADigits: integer): string;
begin
  if ADigits in [1..16] then
  begin {that's the range of sig. digits supported}
    Result := Format('%' +
      IntToStr(ADigits) + '.' +
      IntToStr(ADigits) + 'f', [ARealNumber]);

    Result := Copy(Result, 1, ADigits + 1);
    if (Pos('.', Result) in [0, ADigits + 1]) then
    begin
      Result := Copy(Result, 1, ADigits);
    end; {if}
  end
  else
  begin
    raise Exception.Create('GetSignificantDigits: ' +
      'A number of significant digits out of range');
  end; {if}
end; {--GetSignificantDigits--}

{------------------------------------------------------------------------}
{converts given AValue to the string representing file size, in a way similar
to what Windows Explorer does}

function FormatFileSizeValue(const AValue: integer): string;
begin
  case AValue of
    0..999:
      begin
        Result := IntToStr(AValue) + ' bytes';
      end;
    1000..(MB - 1):
      begin
        Result := GetSignificantDigits(AValue / KB, 3) + 'KB';
      end;
    MB..(GB - 1):
      begin
        Result := GetSignificantDigits(AValue / MB, 3) + 'MB';
      end;
  else
    begin
      Result := GetSignificantDigits(AValue / GB, 3) + 'GB';
    end;
  end; {case}
end; {--FormatFileSizeValue--}

end.

Nincsenek megjegyzések:

Megjegyzés küldése