2010年11月3日水曜日

Purgeもどき

仕事で使っているアプリケーションがExeName_YYYYMMDDHHNNSS.Logのような
ログファイルを一杯作るので、消してくれという依頼があった。

ただし、このフォルダには、上記のログを作るファイルがいくつかああって
3世代程度は残してくれという依頼があった。

昔、仕事で触ってたOpenVmsのファイルシステムにはバージョンというのが
あって、古いバージョンのファイルを消すPurgeコマンドがあり
これとにたようなものが欲しいなということで、とりあえず作ってみた。
(本家には似ても似つかぬ、あくまでももどきですが。)

使い方は、

PurgeMock "パージファイルマスク" /D:パージファイルがあるフォルダ /G:残す世代数

で、/R: を指定することで、パージファイルマスクに正規表現が使用できます。

また、ファイルの更新日時を基準に/G:で指定した数だけ残るよう古い日付のものを
削除しますが、 /T:を指定することでファイルの作成日時を基準にPurgeすることが
できます。

なお、パージファイルマスクにより、複数の異なるファイル名のファイルが削除対象
となりえますが、このツールでは、このような場合のことを想定しておりません。
(/G:で指定した数になるようにばっさり消します。)

以下、ソース(delphi Xeがあればコンパイル可能です。)を置いておきます。
ライセンスは、MITライセンスとします。
ソース使用は自由ですが、無保証とします。

まずはメイン。

program PurgeMock;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  PurgeMockImp in 'PurgeMockImp.pas',
  MidasLib
  ;

function CreatePurgeMockParams : TPurgeMockParams;
var
  PurgeMockParams : TPurgeMockParams;
 i : Integer;
begin

  PurgeMockParams := TPurgeMockParams.Create;
  try
    try
   PurgeMockParams.Init(Paramstr(0));
   for i := 1 to ParamCount -1 do
      begin
       PurgeMockParams.SetPurgeParam(Paramstr(i));
      end;
    except
      PurgeMockParams.Free;
      PurgeMockParams := nil;
    end;

  finally
    Result := PurgeMockParams;
  end;

end;

var
  PurgeMockParams : TPurgeMockParams;
  DoPurgeMock     : TPuregMock;
  Ret : Integer;

begin
  try
    { TODO -oUser -cConsole メイン : ここにコードを記述してください }
    PurgeMockParams := CreatePurgeMockParams;
    if Assigned(PurgeMockParams) then
    begin
      DoPurgeMock := TPuregMock.Create(PurgeMockParams);
      try
        Ret := DoPurgeMock.Purge;
      finally
        DoPurgeMock.Free;
      end;
      PurgeMockParams.Free;
    end;


  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.


次に、パージコマンドの本体

unit PurgeMockImp;

interface

uses
  SysUtils
 ,DB
  ,DBClient
  ;

type
  TPurgeTimeStamp = (ptCreate, ptUpdate);

  TPurgeMockParams = class
  private
    FTargetDir: String;
    FUseRegularExpression: Boolean;
    FFileMask: String;
    FLeavingGenerations: Integer;
    FPurgeTimeStamp: TPurgeTimeStamp;
    procedure SetTargetDir(val: String);
    procedure SetUseRegularExpression(val: Boolean);
    procedure SetFileMask(val: String);
    procedure SetLeavingGenerations(val: Integer);
    procedure SetPurgeTimeStamp(val: TPurgeTimeStamp);
  public
    property TargetDir: String read FTargetDir write SetTargetDir;
    property UseRegularExpression: Boolean read FUseRegularExpression
      write SetUseRegularExpression;
    property FileMask: String read FFileMask write SetFileMask;
    property LeavingGenerations: Integer read FLeavingGenerations
      write SetLeavingGenerations;
    property PurgeTimeStamp: TPurgeTimeStamp read FPurgeTimeStamp
      write SetPurgeTimeStamp;
    procedure Init(ExePath: String);
  procedure SetPurgeParam(PurgeParam : String);
  end;

  TPuregMock = class
  public
    constructor Create(Params: TPurgeMockParams);
    function Purge: Integer;
    destructor Destroy; override;
  strict private
    FPurgeMockParams: TPurgeMockParams;
    FPurgeMockDataSet : TClientDataSet;
    function MakePurgeFileList: Integer;
    function ExecPurge: Integer;
    function CreatePurgeDataSet : TClientDataSet;
  end;

  function TryParseCmdLineSw(ParameterString : String; Sw : Array of String; var ParamValue : String; IgnoreCase: Boolean = true) : boolean;

implementation

Uses
   StrUtils
  ,IOUtils
  ,Types
  ,RegularExpressionsCore
  ;

procedure TPurgeMockParams.SetTargetDir(val: String);
begin
  FTargetDir := val;
end;

procedure TPurgeMockParams.SetUseRegularExpression(val: Boolean);
begin
  FUseRegularExpression := val;
end;

procedure TPurgeMockParams.Init(ExePath: String);
begin

 FTargetDir := ExtractFileDir(ExePath);
 FUseRegularExpression := false;
 FileMask := '*.*';
 FLeavingGenerations := 1;
 FPurgeTimeStamp := ptUpdate;

end;

procedure TPurgeMockParams.SetFileMask(val: String);
begin
  FFileMask := val
end;

procedure TPurgeMockParams.SetLeavingGenerations(val: Integer);
begin
 if Val >= 0 then
  begin
   FLeavingGenerations := val;
  end
  else
  begin
   FLeavingGenerations := 0;
  end;
end;

procedure TPurgeMockParams.SetPurgeParam(PurgeParam: String);
var
  ValueStr : String;
begin

 if TryParseCmdLineSw(PurgeParam,['/D:','-D:'],ValueStr) then
  begin
   FTargetDir := ValueStr;
    Exit;
  end;

 if TryParseCmdLineSw(PurgeParam,['/U:','-U:'],ValueStr) then
  begin
   FUseRegularExpression := true;
    Exit;
  end;

 if TryParseCmdLineSw(PurgeParam,['/G:','-G:'],ValueStr) then
  begin
   LeavingGenerations := StrToIntDef(ValueStr,1);
    Exit;
  end;

 if TryParseCmdLineSw(PurgeParam,['/T:','-T:'],ValueStr) then
  begin
   FPurgeTimeStamp := ptCreate;
    Exit;
  end;

 if TryParseCmdLineSw(PurgeParam,[],ValueStr) then
  begin
    FFileMask := ValueStr;
    Exit;
  end;

end;

procedure TPurgeMockParams.SetPurgeTimeStamp(val: TPurgeTimeStamp);
begin
  FPurgeTimeStamp := val;
end;

constructor TPuregMock.Create(Params: TPurgeMockParams);
begin
  inherited Create;
  FPurgeMockParams := Params;
end;

function TPuregMock.Purge: Integer;
begin

  Result := -1;

 FPurgeMockDataSet := CreatePurgeDataSet;
  if Assigned(FPurgeMockDataSet) then
  begin
    if MakePurgeFileList > 0 then
    begin
      Result := ExecPurge;
    end;
    FPurgeMockDataSet.EmptyDataSet;
    FPurgeMockDataSet.Active := false;
    FPurgeMockDataSet.Free;
  end;

end;

function TPuregMock.CreatePurgeDataSet: TClientDataSet;
var
 PurgeDataSet : TClientDataSet;
 FD : TFieldDef;
begin
 PurgeDataSet := TClientDataSet.Create(nil);
  try
    try
   FD := PurgeDataSet.FieldDefs.AddFieldDef;
      FD.Name := 'FileName';
      FD.DataType := ftString;
      FD.Size := 1024;

   FD := PurgeDataSet.FieldDefs.AddFieldDef;
      FD.Name := 'TimeStamp';
      FD.DataType := ftDatetime;

      PurgeDataSet.CreateDataSet;
      PurgeDataSet.AddIndex('Purge_Idx','TimeStamp;FileName',[]);
      PurgeDataSet.IndexName := 'Purge_Idx';

    Except
     if Assigned(PurgeDataSet) then PurgeDataSet.Free;
      PurgeDataSet := nil;
    end;
  finally
   Result := PurgeDataSet;
  end;
end;

destructor TPuregMock.Destroy;
begin
  if Assigned(FPurgeMockParams) then
  begin
    FPurgeMockParams := nil;
  end;

  inherited Destroy;
end;

function TPuregMock.MakePurgeFileList: Integer;
var
 SearchOption: TSearchOption;
 SearchDir : TDirectory;
  SearchFileMask : String;
  LList: TStringDynArray;
  FileFullName,FileName : String;
  CanAddList : Boolean;
  PerlRegEx: TPerlRegEx;
  PurgeFile : TFile;

begin

  Result := 0;

  //使っても使わなくても正規表現用のクラスをオープンしておく。
  PerlRegEx:= TPerlRegEx.Create;
  try
    SearchOption := TSearchOption.soTopDirectoryOnly;
    SearchFileMask := FPurgeMockParams.FileMask;


    if FPurgeMockParams.UseRegularExpression then
    begin
      PerlRegEx.RegEx := SearchFileMask;
      SearchFileMask := '*.*';
    end;

    try
      LList := SearchDir.GetFiles(FPurgeMockParams.FTargetDir,SearchFileMask, SearchOption);
      FPurgeMockDataSet.Active := true;
      for FileFullName in LList do
      begin
        CanAddList := true;
        if FPurgeMockParams.UseRegularExpression then
        begin
          FileName := ExtractFileName(FileFullName);
          PerlRegEx.Subject := FileName;
          CanAddList := PerlRegEx.Match;
        end;

        if CanAddList then
        begin
          //パージ対象リストに追加
          FPurgeMockDataSet.Append;
          FPurgeMockDataSet.FieldByName('FileName').AsString := FileFullName;
          if FPurgeMockParams.FPurgeTimeStamp = ptCreate then
          begin
           FPurgeMockDataSet.FieldByName('TimeStamp').AsDateTime := PurgeFile.GetCreationTime(FileFullName);
          end
          else
          begin
           FPurgeMockDataSet.FieldByName('TimeStamp').AsDateTime := PurgeFile.GetLastAccessTime(FileFullName);
          end;
          FPurgeMockDataSet.Post;
        end;
      end;
      Result := FPurgeMockDataSet.RecordCount;

    except
      Result := -1;
    end;
  finally
  if Assigned(PerlRegEx) then PerlRegEx.Free;

  end;
end;

function TPuregMock.ExecPurge: Integer;
var
 DeleteCount : integer;
  DeleteFileName : string;
  PurgeFile : TFile;
begin
  DeleteCount := 0;

  try
    FPurgeMockDataSet.Active := true;
    FPurgeMockDataSet.First;

    while not(FPurgeMockDataSet.Eof) do
    begin
      DeleteFileName := FPurgeMockDataSet.FieldByName('FileName').AsString;
      PurgeFile.Delete(DeleteFileName);
      inc(DeleteCount);

      //レコードをDeleteした場合は、Next不要なので注意
      FPurgeMockDataSet.Delete;
      if FPurgeMockDataSet.RecordCount <= FPurgeMockParams.LeavingGenerations then Break;
    end;
   Result := DeleteCount;

  Except
   Result := -1;
  end;
end;

function TryParseCmdLineSw(ParameterString : String;
                           Sw : Array of String;
                           var ParamValue : String;
                           IgnoreCase: Boolean) : boolean;
var
 i : integer;
begin

 Result := false;

  if Length(Sw) = 0 then
  begin
   //SW指定がない場合はそのままの文字列を返す
   ParamValue := ParameterString;
    Result := true;
  end
  else
  begin
   for i := low(Sw) to high(Sw) do
    begin
      if IgnoreCase then
      begin
        if StartsText(Sw[i],ParameterString) then
        begin
          Result := true;
          ParamValue := ReplaceText(ParameterString,Sw[i],'');
          Break;
        end;
      end
      else
      begin
        if StartsStr(Sw[i],ParameterString) then
        begin
          Result := true;
          ParamValue := ReplaceStr(ParameterString,Sw[i],'');
          Break;
        end;
      end;
    end;
  end;

end;

end.

1 件のコメント:

匿名 さんのコメント...

こんにちは、通りすがりのDelphiユーザーです。
ログの一定期間の自動消去は私もよく使います。フリーのいいものがないので自作しましたが、コンポーネントになっていて組み込みになっていると使いやすいですよね。スケジューラーで自動起動して定期的に消去できると最高です。