{ Double commander ------------------------------------------------------------------------- Manager for commands associated to file extension. Copyright (C) 2008-2018 Alexander Koblov (alexx2000@mail.ru) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Original comment: ---------------------------- Seksi Commander ---------------------------- Licence : GNU GPL v 2.0 Author : radek.cervinka@centrum.cz storing commands (by file extensions) } unit uExts; {$mode objfpc}{$H+} interface uses Graphics, Classes, Contnrs, uFile; type { What constitutes our basic info for the external actual action } TExtActionCommand = class FActionName: string; FCommandName: string; FParams: string; FStartPath: string; FIconIndex: integer; FIconBitmap: Graphics.TBitmap; public constructor Create(ParamActionName, ParamCommandName, ParamParams, ParamStartPath: string); destructor Destroy; override; function CloneExtAction: TExtActionCommand; property ActionName: string read FActionName write FActionName; property CommandName: string read FCommandName write FCommandName; property Params: string read FParams write FParams; property StartPath: string read FStartPath write FStartPath; property IconIndex: integer read FIconIndex write FIconIndex; property IconBitmap: Graphics.TBitmap read FIconBitmap write FIconBitmap; end; { Each file type may have more than one possible associated action ("TExtActionCommand"). This class is to hold a collection of this } TExtActionList = class(TList) private function GetExtActionCommand(Index: integer): TExtActionCommand; public constructor Create; procedure Clear; override; function Add(ExtActionCommand: TExtActionCommand): integer; procedure Insert(Index: integer; ExtActionCommand: TExtActionCommand); procedure DeleteExtActionCommand(Index: integer); property ExtActionCommand[Index: integer]: TExtActionCommand read GetExtActionCommand; end; { Class for storage actions by file extensions } TExtAction = class Name: string; //en< File type name, for example "Hyper text documents" Icon: string; //en< Path to icon IconIndex: integer; Extensions: TStringList; //en< List of extensions ActionList: TExtActionList; public constructor Create; destructor Destroy; override; function GetIconListForStorage: string; procedure SetIconListFromStorage(sStorage: string); end; { Main class for storage actions list by file extensions } TExts = class private function GetCount: integer; function GetItems(Index: integer): TExtAction; procedure LegacyLoadFromFile(const sName: string); protected FExtList: TObjectList; public constructor Create; destructor Destroy; override; procedure Clear; function AddItem(AExtAction: TExtAction): integer; procedure DeleteItem(Index: integer); procedure MoveItem(SrcIndex, DestIndex: integer); function Load: boolean; function LoadXMLFile: boolean; procedure SaveXMLFile; function GetExtActionCmd(aFile: TFile; const sActionName: string; var sCmd: string; var sParams: string; var sStartPath: string): boolean; function GetExtActions(aFile: TFile; paramActionList: TExtActionList; pIndexOfFirstPossibleFileType: PInteger = nil; bWantedAllActions: boolean = False): boolean; function ComputeSignature(Seed:dword=$00000000): dword; property Count: integer read GetCount; property Items[Index: integer]: TExtAction read GetItems; property FileType[Index: integer]: TExtAction read GetItems; end; const cMaskDefault = 'default'; cMaskFolder = 'folder'; cMaskFile = 'file'; implementation uses DCXmlConfig, uDCVersion, uGlobsPaths, uDCUtils, crc, uLng, SysUtils, uLog, DCClassesUtf8, DCOSUtils, strUtils; { TExtActionCommand.Create } constructor TExtActionCommand.Create(ParamActionName, ParamCommandName, ParamParams, ParamStartPath: string); begin inherited Create; FActionName := ParamActionName; FCommandName := ParamCommandName; FParams := ParamParams; FStartPath := ParamStartPath; FIconIndex := -1; // <--IconIndex is used only in "uShellContextMenu" to show an icon next to the command AND is filled correctly when doing "TExts.GetExtActions" FIconBitmap := nil; // <--IconBitmap is used only in "uShellContextMenu" to show an icon next to the command AND is filled correctly when doing "CreateActionSubMenu" from "uShellContextMenu" end; { TExtActionCommand.Destroy } destructor TExtActionCommand.Destroy; begin if Assigned(FIconBitmap) then FreeAndNil(FIconBitmap); inherited; end; { TExtActionCommand.CloneExtAction } function TExtActionCommand.CloneExtAction: TExtActionCommand; begin Result := TExtActionCommand.Create(self.FActionName, self.FCommandName, self.FParams, self.FStartPath); end; { TExtActionList.Create } constructor TExtActionList.Create; begin inherited Create; end; { TExtActionList.Clear } procedure TExtActionList.Clear; var i: integer; begin for i := 0 to Count - 1 do ExtActionCommand[i].Free; inherited Clear; end; { TExtActionList.Add } function TExtActionList.Add(ExtActionCommand: TExtActionCommand): integer; begin Result := inherited Add(ExtActionCommand); end; { TExtActionList.Insert } procedure TExtActionList.Insert(Index: integer; ExtActionCommand: TExtActionCommand); begin inherited Insert(Index, ExtActionCommand); end; { TExtActionList.DeleteExtActionCommand } procedure TExtActionList.DeleteExtActionCommand(Index: integer); begin ExtActionCommand[Index].Free; Delete(Index); end; { TExtActionList.GetExtActionCommand } function TExtActionList.GetExtActionCommand(Index: integer): TExtActionCommand; begin Result := TExtActionCommand(Items[Index]); end; constructor TExtAction.Create; begin inherited Create; Extensions := TStringList.Create; Extensions.CaseSensitive := False; ActionList := TExtActionList.Create; end; destructor TExtAction.Destroy; begin if Assigned(Extensions) then FreeAndNil(Extensions); if Assigned(ActionList) then FreeAndNil(ActionList); inherited; end; { TExtAction.GetIconListForStorage } function TExtAction.GetIconListForStorage: string; var iExtension: integer; begin Result := ''; if Extensions.Count = 0 then Result := rsMsgUserDidNotSetExtension else for iExtension := 0 to pred(Extensions.Count) do if Result = '' then Result := Extensions[iExtension] else Result := Result + '|' + Extensions[iExtension]; end; { TExtAction.SetIconListFromStorage } procedure TExtAction.SetIconListFromStorage(sStorage: string); var PosPipe, LastPosPipe: integer; begin LastPosPipe := 0; repeat PosPipe := posEx('|', sStorage, LastPosPipe + 1); if PosPipe <> 0 then begin Extensions.add(copy(sStorage, LastPosPipe + 1, ((PosPipe - LastPosPipe) - 1))); LastPosPipe := PosPipe; end; until PosPipe = 0; if length(sStorage) > LastPosPipe then Extensions.add(copy(sStorage, LastPosPipe + 1, (length(sStorage) - LastPosPipe))); if Extensions.Count = 0 then Extensions.Add(rsMsgUserDidNotSetExtension); end; { TExts.LegacyLoadFromFile } //We need to keep this routine to be able to load "old legacy format" of the //file associated action based on file extension that was using DC originally. procedure TExts.LegacyLoadFromFile(const sName: string); var extFile: TStringListEx; sLine, s, sExt: string; extCurrentFileType: TExtAction; I, iIndex: integer; sCommandName, sEndingPart, sCommandCmd, sParams: string; begin extFile := TStringListEx.Create; try extFile.LoadFromFile(sName); extCurrentFileType := nil; for I := 0 to extFile.Count - 1 do begin sLine := extFile.Strings[I]; sLine := Trim(sLine); if (sLine = '') or (sLine[1] = '#') then Continue; if sLine[1] = '[' then begin extCurrentFileType := TExtAction.Create; FExtList.Add(extCurrentFileType); iIndex := pos(']', sLine); if iIndex > 0 then sLine := Copy(sLine, 1, iIndex) else logWrite(Format(rsExtsClosedBracketNoFound, [sLine])); extCurrentFileType.Name:=sLine; // Just in case we don't have a name later on, let's named the file type based on the extension defined. // fill extensions list s := sLine; Delete(s, 1, 1); // Delete '[' Delete(s, Length(s), 1); // Delete ']' s := s + '|'; while Pos('|', s) <> 0 do begin iIndex := Pos('|', s); sExt := Copy(s, 1, iIndex - 1); Delete(s, 1, iIndex); extCurrentFileType.Extensions.Add(sExt); end; end // end if.. '[' else begin // this must be a command if not assigned(extCurrentFileType) then begin logWrite(Format(rsExtsCommandWithNoExt, [sLine])); Continue; end; // now set command to lowercase s := sLine; for iIndex := 1 to Length(s) do begin if s[iIndex] = '=' then Break; s[iIndex] := LowerCase(s[iIndex]); end; if Pos('name', s) = 1 then // File type name extCurrentFileType.Name := Copy(sLine, iIndex + 1, Length(sLine)) else if Pos('icon', s) = 1 then // File type icon extCurrentFileType.Icon := Copy(sLine, iIndex + 1, Length(sLine)) else // action begin sCommandName := Copy(sLine, 1, iIndex - 1); sEndingPart := Copy(sLine, iIndex + 1, Length(sLine)); try SplitCmdLineToCmdParams(sEndingPart, sCommandCmd, sParams); except sCommandCmd := ' '+sEndingPart; //Just in case the user has something wrong in his settings, LIKE a missing ending quote... sParams := ''; end; sCommandCmd := Trim(sCommandCmd); sParams := Trim(sParams); extCurrentFileType.ActionList.Add(TExtActionCommand.Create(sCommandName, sCommandCmd, sParams, '')); end; end; end; finally extFile.Free; end; end; function TExts.GetExtActions(aFile: TFile; paramActionList: TExtActionList; pIndexOfFirstPossibleFileType: PInteger = nil; bWantedAllActions: boolean = False): boolean; var I, iActionNo: integer; sMask: string; ExtActionCommand: TExtActionCommand; begin if pIndexOfFirstPossibleFileType <> nil then pIndexOfFirstPossibleFileType^ := -1; Result := False; if aFile.IsDirectory or aFile.IsLinkToDirectory then sMask := cMaskFolder else sMask := LowerCase(aFile.Extension); if Length(sMask) <> 0 then for I := 0 to FExtList.Count - 1 do with GetItems(i) do begin if Extensions.IndexOf(sMask) >= 0 then begin if paramActionList.Count > 0 then paramActionList.Add(TExtActionCommand.Create('-', '', '', '')); for iActionNo := 0 to pred(ActionList.Count) do begin ExtActionCommand := ActionList.ExtActionCommand[iActionNo].CloneExtAction; ExtActionCommand.IconIndex := IconIndex; paramActionList.Add(ExtActionCommand); end; if pIndexOfFirstPossibleFileType <> nil then if pIndexOfFirstPossibleFileType^ = -1 then pIndexOfFirstPossibleFileType^ := I; Result := True; if not bWantedAllActions then Break; end; end; if sMask = cMaskFolder then Exit; for I := 0 to FExtList.Count - 1 do with GetItems(i) do begin if Extensions.IndexOf(cMaskFile) >= 0 then begin if paramActionList.Count > 0 then paramActionList.Add(TExtActionCommand.Create('-', '', '', '')); for iActionNo := 0 to pred(ActionList.Count) do begin ExtActionCommand := ActionList.ExtActionCommand[iActionNo].CloneExtAction; ExtActionCommand.IconIndex := IconIndex; paramActionList.Add(ExtActionCommand); end; if pIndexOfFirstPossibleFileType <> nil then if pIndexOfFirstPossibleFileType^ = -1 then pIndexOfFirstPossibleFileType^ := I; Result := True; if not bWantedAllActions then Break; end; end; end; function TExts.GetCount: integer; begin Result := FExtList.Count; end; function TExts.GetItems(Index: integer): TExtAction; begin Result := TExtAction(FExtList.Items[Index]); end; constructor TExts.Create; begin inherited Create; FExtList := TObjectList.Create; end; destructor TExts.Destroy; begin if assigned(FExtList) then FreeAndNil(FExtList); inherited; end; procedure TExts.Clear; begin FExtList.Clear; end; function TExts.AddItem(AExtAction: TExtAction): integer; begin Result := FExtList.Add(AExtAction); end; procedure TExts.DeleteItem(Index: integer); begin FExtList.Delete(Index); end; procedure TExts.MoveItem(SrcIndex, DestIndex: integer); begin FExtList.Move(SrcIndex, DestIndex); end; function TExts.GetExtActionCmd(aFile: TFile; const sActionName: string; var sCmd: string; var sParams: string; var sStartPath: string): boolean; var I: integer; sMask: string; iAction: integer; begin Result := False; sCmd := ''; sParams := ''; sStartPath := ''; if aFile.IsDirectory or aFile.IsLinkToDirectory then sMask := cMaskFolder else sMask := LowerCase(aFile.Extension); if Length(sMask) <> 0 then begin for I := 0 to FExtList.Count - 1 do with GetItems(I) do begin if Extensions.IndexOf(sMask) >= 0 then begin iAction := 0; while (iAction < ActionList.Count) and (not Result) do begin if UpperCase(ActionList.ExtActionCommand[iAction].ActionName) = UpperCase(sActionName) then begin sCmd := ActionList.ExtActionCommand[iAction].CommandName; sParams := ActionList.ExtActionCommand[iAction].Params; sStartPath := ActionList.ExtActionCommand[iAction].StartPath; Result := True; Exit; end else begin Inc(iAction); end; end; end; end; end; // if command not found then try to find default command for I := 0 to FExtList.Count - 1 do with GetItems(I) do begin if Extensions.IndexOf(cMaskDefault) >= 0 then begin iAction := 0; while (iAction < ActionList.Count) and (not Result) do begin if UpperCase(ActionList.ExtActionCommand[iAction].ActionName) = UpperCase(sActionName) then begin sCmd := ActionList.ExtActionCommand[iAction].CommandName; sParams := ActionList.ExtActionCommand[iAction].Params; sStartPath := ActionList.ExtActionCommand[iAction].StartPath; Result := True; Exit; end else begin Inc(iAction); end; end; end; end; end; { TExts.ComputeSignature } function TExts.ComputeSignature(Seed:dword): dword; var iExtType, iExtension, iAction: integer; begin Result := Seed; for iExtType := 0 to pred(Count) do begin Result := crc32(Result, @Items[iExtType].Name[1], length(Items[iExtType].Name)); if length(Items[iExtType].Icon) > 0 then Result := crc32(Result, @Items[iExtType].Icon[1], length(Items[iExtType].Icon)); for iExtension := 0 to pred(Items[iExtType].Extensions.Count) do if length(Items[iExtType].Extensions.Strings[iExtension]) > 0 then Result := crc32(Result, @Items[iExtType].Extensions.Strings[iExtension][1], length(Items[iExtType].Extensions.Strings[iExtension])); for iAction := 0 to pred(Items[iExtType].ActionList.Count) do begin if length(Items[iExtType].ActionList.ExtActionCommand[iAction].FActionName) > 0 then Result := crc32(Result, @Items[iExtType].ActionList.ExtActionCommand[iAction].FActionName[1], length(Items[iExtType].ActionList.ExtActionCommand[iAction].FActionName)); if length(Items[iExtType].ActionList.ExtActionCommand[iAction].FCommandName) > 0 then Result := crc32(Result, @Items[iExtType].ActionList.ExtActionCommand[iAction].FCommandName[1], length(Items[iExtType].ActionList.ExtActionCommand[iAction].FCommandName)); if length(Items[iExtType].ActionList.ExtActionCommand[iAction].FParams) > 0 then Result := crc32(Result, @Items[iExtType].ActionList.ExtActionCommand[iAction].FParams[1], length(Items[iExtType].ActionList.ExtActionCommand[iAction].FParams)); if length(Items[iExtType].ActionList.ExtActionCommand[iAction].FStartPath) > 0 then Result := crc32(Result, @Items[iExtType].ActionList.ExtActionCommand[iAction].FStartPath[1], length(Items[iExtType].ActionList.ExtActionCommand[iAction].FStartPath)); end; end; end; { TExts.SaveXMLFile } procedure TExts.SaveXMLFile; var ActionList: TExtActionList; iFileType, iAction: Integer; ExtXMLSettings: TXmlConfig = nil; Root, Node, SubNode, SubSubNode: TXmlNode; begin ExtXMLSettings := TXmlConfig.Create(gpCfgDir + gcfExtensionAssociation); try with ExtXMLSettings do begin Root := ExtXMLSettings.RootNode; SetAttr(Root, 'DCVersion', dcVersion); Node := FindNode(Root, 'ExtensionAssociation', True); ClearNode(Node); { Each file type has its own extensions } for iFileType := 0 to Pred(Count) do begin SubNode := AddNode(Node, 'FileType'); SetValue(SubNode, 'Name', FileType[iFileType].Name); SetValue(SubNode, 'IconFile', FileType[iFileType].Icon); SetValue(SubNode, 'ExtensionList', FileType[iFileType].GetIconListForStorage); SubNode := AddNode(SubNode, 'Actions'); ActionList := FileType[iFileType].ActionList; for iAction := 0 to Pred(ActionList.Count) do begin SubSubNode := AddNode(SubNode, 'Action'); SetValue(SubSubNode, 'Name', ActionList.ExtActionCommand[iAction].ActionName); if ActionList.ExtActionCommand[iAction].CommandName <> '' then SetValue(SubSubNode, 'Command', ActionList.ExtActionCommand[iAction].CommandName); if ActionList.ExtActionCommand[iAction].Params <> '' then SetValue(SubSubNode, 'Params', ActionList.ExtActionCommand[iAction].Params); if ActionList.ExtActionCommand[iAction].StartPath <> '' then SetValue(SubSubNode, 'StartPath', ActionList.ExtActionCommand[iAction].StartPath); end; end; end; ExtXMLSettings.Save; finally ExtXMLSettings.Free; end; end; { TExts.Load} function TExts.Load: boolean; begin Result := False; try if (mbFileExists(gpCfgDir + 'doublecmd.ext')) AND (not mbFileExists(gpCfgDir + gcfExtensionAssociation)) then begin LegacyLoadFromFile(gpCfgDir + 'doublecmd.ext'); SaveXmlFile; mbRenameFile(gpCfgDir + 'doublecmd.ext', gpCfgDir + 'doublecmd.ext.obsolete'); Result := True; end else begin Result := LoadXMLFile; end; except Result := False; end; end; { TExts.LoadXMLFile } function TExts.LoadXMLFile: boolean; var extCurrentFileType: TExtAction; ExtXMLSettings: TXmlConfig = nil; Node, SubNode, SubSubNode: TXmlNode; sName, sIconFilename, sExtensionList, sActionName, sCommandName, sParams, sStartPath: string; begin Result := False; try ExtXMLSettings := TXmlConfig.Create(gpCfgDir + gcfExtensionAssociation); try ExtXMLSettings.Load; with ExtXMLSettings do begin Node := FindNode(ExtXMLSettings.RootNode, 'ExtensionAssociation'); if Assigned(Node) then begin SubNode := Node.FirstChild; while Assigned(SubNode) do begin if SubNode.CompareName('FileType') = 0 then begin sName := ExtXMLSettings.GetValue(SubNode, 'Name', rsMsgUserDidNotSetName); if sName <> rsMsgUserDidNotSetName then begin sIconFilename := ExtXMLSettings.GetValue(SubNode, 'IconFile', ''); sExtensionList := ExtXMLSettings.GetValue(SubNode, 'ExtensionList', rsMsgUserDidNotSetExtension); extCurrentFileType := TExtAction.Create; extCurrentFileType.Name := sName; extCurrentFileType.Icon := sIconFilename; extCurrentFileType.SetIconListFromStorage(sExtensionList); SubSubNode := FindNode(SubNode, 'Actions'); if Assigned(SubSubNode) then begin SubSubNode := SubSubNode.FirstChild; while Assigned(SubSubNode) do begin if SubSubNode.CompareName('Action') = 0 then begin sActionName := ExtXMLSettings.GetValue(SubSubNode, 'Name', rsMsgUserDidNotSetName); sCommandName := ExtXMLSettings.GetValue(SubSubNode, 'Command', ''); sParams := ExtXMLSettings.GetValue(SubSubNode, 'Params', ''); sStartPath := ExtXMLSettings.GetValue(SubSubNode, 'StartPath', ''); extCurrentFileType.ActionList.Add(TExtActionCommand.Create(sActionName, sCommandName, sParams, sStartPath)); end; SubSubNode := SubSubNode.NextSibling; end; end; AddItem(extCurrentFileType); end; end; SubNode := SubNode.NextSibling; end; end; end; finally ExtXMLSettings.Free; end; Result := True; except Result := False; end; end; end. //Cleaner les >Action@//Utiliser ExtFileType comme nom au lieu de Action car pas tout de suite une action//Remplacer le Savefile by SaveXMLFile