{
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