doublecmd/src/fsplitter.pas
2019-01-27 19:56:57 +00:00

370 lines
13 KiB
ObjectPascal

{
Double Commander
-------------------------------------------------------------------------
Take a single file and split it in part based on few parameters.
Copyright (C) 2007-2019 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 <http://www.gnu.org/licenses/>.
}
unit fSplitter;
{$mode objfpc}{$H+}
interface
uses
//Lazarus, Free-Pascal, etc.
SysUtils, Classes, Controls, Forms, Dialogs, StdCtrls, Buttons, Menus,
EditBtn, ExtCtrls,
//DC
fButtonForm, uFileSource, uFile, KASComboBox;
type
{ TfrmSplitter }
TfrmSplitter = class(TfrmButtonForm)
btnRelativeFTChoice: TSpeedButton;
edDirTarget: TDirectoryEdit;
lbDirTarget: TLabel;
teNumberParts: TEdit;
lblNumberParts: TLabel;
grbxSize: TGroupBox;
cmbxSize: TComboBoxAutoWidth;
rbtnKiloB: TRadioButton;
rbtnMegaB: TRadioButton;
rbtnGigaB: TRadioButton;
rbtnByte: TRadioButton;
cbRequireACRC32VerificationFile: TCheckBox;
pmPathHelper: TPopupMenu;
procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure SetNumberOfPart;
procedure SetSizeOfPart;
procedure cmbxSizeChange(Sender: TObject);
procedure btnRelativeFTChoiceClick(Sender: TObject);
procedure rbtnByteChange(Sender: TObject);
procedure teNumberPartsChange(Sender: TObject);
private
FFileName: String;
iVolumeSize: Int64;
MyModalResult: integer;
iVolumeNumber: Integer;
function StrConvert(sExpression: String): Int64;
public
{ Public declarations }
end;
{ ShowSplitterFileForm:
"TMainCommands.cm_FileSpliter" function from "uMainCommands.pas" is calling this routine.}
function ShowSplitterFileForm(TheOwner: TComponent; aFileSource: IFileSource; var aFile: TFile; const TargetPath: String): Boolean;
implementation
{$R *.lfm}
uses
//Lazarus, Free-Pascal, etc.
LazUTF8, LCLType, LCLProc,
//DC
uTypes, DCStrUtils, uLng, uFileProcs, uOperationsManager,
uFileSourceSplitOperation, uShowMsg, DCOSUtils, uGlobs, uSpecialDir, uDCUtils;
{ ShowSplitterFileForm:
"TMainCommands.cm_FileSpliter" function from "uMainCommands.pas" is calling this routine.}
function ShowSplitterFileForm(TheOwner: TComponent; aFileSource: IFileSource; var aFile: TFile; const TargetPath: String): Boolean;
var
frmSplitter:TfrmSplitter;
Operation: TFileSourceSplitOperation = nil;
begin
frmSplitter:=TfrmSplitter.Create(TheOwner); //Did not use the "with..." here to make absolutely sure of what is referenced in the following.
try
frmSplitter.FFileName:= aFile.FullPath;
frmSplitter.edDirTarget.Text:= TargetPath;
frmSplitter.SetNumberOfPart;
// Show form
Result:= (frmSplitter.ShowModal = mrOK);
if Result then
begin
try
Operation:= aFileSource.CreateSplitOperation(aFile, frmSplitter.edDirTarget.Text) as TFileSourceSplitOperation;
if Assigned(Operation) then
begin
Operation.VolumeSize:= frmSplitter.iVolumeSize;
Operation.VolumeNumber:= frmSplitter.iVolumeNumber;
Operation.RequireACRC32VerificationFile:= frmSplitter.cbRequireACRC32VerificationFile.Checked;
Operation.AutomaticSplitMode:=(frmSplitter.cmbxSize.ItemIndex=0);
OperationsManager.AddOperation(Operation, frmSplitter.QueueIdentifier, False);
end;
finally
FreeAndNil(aFile);
end;
end;
finally
frmSplitter.Free;
end;
end;
{ TfrmSplitter.SetNumberOfPart }
procedure TfrmSplitter.SetNumberOfPart;
begin
if cmbxSize.ItemIndex<>0 then
begin
if StrConvert(cmbxSize.Text)>0 then
begin
if mbFileSize(FFileName) mod StrConvert(cmbxSize.Text)>0 then
teNumberParts.Text:= IntToStr( (mbFileSize(FFileName) div StrConvert(cmbxSize.Text)) +1)
else
teNumberParts.Text:= IntToStr(mbFileSize(FFileName) div StrConvert(cmbxSize.Text));
end
else
begin
teNumberParts.Text:=rsSimpleWordError;
end;
end
else
begin
teNumberParts.Text:=rsMSgUndeterminedNumberOfFile;
end;
end;
{ TfrmSplitter.SetSizeOfPart }
procedure TfrmSplitter.SetSizeOfPart;
begin
if StrToInt64Def(teNumberParts.Text,0)>0 then
begin
if mbFileSize(FFileName) mod StrToInt64Def(teNumberParts.Text,0)>0 then
cmbxSize.Text := IntToStr(mbFileSize(FFileName) div StrToInt64Def(teNumberParts.Text, 0) + 1)
else
cmbxSize.Text := IntToStr(mbFileSize(FFileName) div StrToInt64Def(teNumberParts.Text, 0));
rbtnByte.Checked := True;
rbtnByte.Enabled := True;
rbtnKiloB.Enabled := True;
rbtnMegaB.Enabled := True;
rbtnGigaB.Enabled := True;
end
else
begin
cmbxSize.Text:=rsSimpleWordError;
end;
end;
{ TfrmSplitter.StrConvert }
//Let's do a basic conversion that maybe is not a full idiot-proof, but versatile and simple enough to fit in a few lines.
function TfrmSplitter.StrConvert(sExpression: String): Int64;
var
iMult: int64 = 1;
bUseRadioButtons: boolean = True;
procedure CheckIfMemSizeAndSetMultiplicator(sExpressionToCheck:string; iMultiplicatorToSetIfAny:int64);
var
iSeekPos: integer;
begin
iSeekPos := pos(UTF8LowerCase(sExpressionToCheck), sExpression);
if iSeekPos <> 0 then
begin
iMult := iMultiplicatorToSetIfAny;
sExpression := UTF8LeftStr(sExpression, pred(iSeekPos));
bUseRadioButtons := False;
end;
end;
begin
//1.Let's place string in lowercase to avoid any later problem.
sExpression := UTF8LowerCase(sExpression);
//2.Let's check first if we have the personalized unit in the expression.
// We check first since they may include spaces and byte suffix.
CheckIfMemSizeAndSetMultiplicator(gSizeDisplayUnits[fsfPersonalizedByte], 1);
CheckIfMemSizeAndSetMultiplicator(gSizeDisplayUnits[fsfPersonalizedKilo], 1024);
CheckIfMemSizeAndSetMultiplicator(gSizeDisplayUnits[fsfPersonalizedMega], 1024*1024);
CheckIfMemSizeAndSetMultiplicator(gSizeDisplayUnits[fsfPersonalizedGiga], 1024*1024*1024);
//4.Let's check if there are single letter multiplier or byte suffix.
CheckIfMemSizeAndSetMultiplicator(rsLegacyOperationByteSuffixLetter, 1);
CheckIfMemSizeAndSetMultiplicator(rsLegacyDisplaySizeSingleLetterKilo, 1024);
CheckIfMemSizeAndSetMultiplicator(rsLegacyDisplaySizeSingleLetterMega, 1024*1024);
CheckIfMemSizeAndSetMultiplicator(rsLegacyDisplaySizeSingleLetterGiga, 1024*1024*1024);
//5. Well... It looks like the pre-defined disk size strings has not been translated in all languages so let's simplify with english values...
//NO NEED TO TRANSLATE THESE ONES! Either translate all disk size strings and/or accept that english abbreviation always work here.
CheckIfMemSizeAndSetMultiplicator('B', 1);
CheckIfMemSizeAndSetMultiplicator('K', 1024);
CheckIfMemSizeAndSetMultiplicator('M', 1024*1024);
CheckIfMemSizeAndSetMultiplicator('G', 1024*1024*1024);
//5.We remove the spaces since they are irrevelant.
sExpression := UTF8StringReplace(sExpression, ' ', '', [rfReplaceAll]);
//6.If we return a number here, let's disable the unit selector below.
if cmbxSize.Focused then
begin
rbtnByte.Enabled := bUseRadioButtons;
rbtnKiloB.Enabled := bUseRadioButtons;
rbtnMegaB.Enabled := bUseRadioButtons;
rbtnGigaB.Enabled := bUseRadioButtons;
end;
//7.If we return a number here, let's disable the unit selector below.
if bUseRadioButtons then
begin
if rbtnKiloB.Checked then iMult:=1024;
if rbtnMegaB.Checked then iMult:=1024*1024;
if rbtnGigaB.Checked then iMult:=1024*1024*1024;
end;
//7.Since we're now supposed to have just numbers in our string, we should be ready to do our conversion.
Result := StrToInt64Def(sExpression, 0) * iMult;
end;
{ TfrmSplitter.FormCreate }
procedure TfrmSplitter.FormCreate(Sender: TObject);
begin
InitPropStorage(Self); // Initialize property storage
MyModalResult:=mrCancel;
gSpecialDirList.PopulateMenuWithSpecialDir(pmPathHelper,mp_PATHHELPER,nil);
ParseLineToList(rsSplitPreDefinedSizes, cmbxSize.Items);
end;
procedure TfrmSplitter.FormShow(Sender: TObject);
begin
if (rbtnGigaB.Left + rbtnGigaB.Width > cmbxSize.Left + cmbxSize.Width) then
begin
cmbxSize.AnchorParallel(akRight, 0, rbtnGigaB);
end;
end;
{ TfrmSplitter.rbtnByteChange }
procedure TfrmSplitter.rbtnByteChange(Sender: TObject);
const
sDigits:string='0123456789';
var
iFirstNonDigit: integer = 0;
iIndex: integer;
sExpression, sSanitize: string;
begin
if rbtnByte.focused OR rbtnKiloB.focused OR rbtnMegaB.focused OR rbtnGigaB.focused then
begin
if TRadioButton(Sender).Checked then
begin
sExpression := UTF8StringReplace(cmbxSize.Text, ' ', '', [rfIgnoreCase , rfReplaceAll]);
sSanitize := '';
iFirstNonDigit := 0;
for iIndex := 1 to UTF8Length(sExpression) do
begin
if (UTF8Pos(UTF8Copy(sExpression, iIndex, 1), sDigits) = 0) then
begin
if iFirstNonDigit = 0 then
iFirstNonDigit := iIndex;
end
else
begin
if iIndex=UTF8Length(sExpression) then iFirstNonDigit := succ(iIndex);
end;
end;
if iFirstNonDigit <> 0 then sSanitize:=UTF8LeftStr(sExpression, pred(iFirstNonDigit));
cmbxSize.Text := sSanitize;
SetNumberOfPart;
end;
end;
end;
{ TfrmSplitter.btnRelativeFTChoiceClick }
procedure TfrmSplitter.btnRelativeFTChoiceClick(Sender: TObject);
begin
edDirTarget.SetFocus;
gSpecialDirList.SetSpecialDirRecipientAndItsType(edDirTarget,pfPATH);
pmPathHelper.PopUp(Mouse.CursorPos.X, Mouse.CursorPos.Y);
end;
{ TfrmSplitter.cmbxSizeChange }
procedure TfrmSplitter.cmbxSizeChange(Sender: TObject);
begin
if cmbxSize.Focused then //Do the function ONLY-IF it's the result of someone typing in the field
begin
if cmbxSize.ItemIndex<>0 then
begin
SetNumberOfPart;
end
else
begin
teNumberParts.Text:='';
if teNumberParts.CanFocus then teNumberParts.SetFocus;
SetSizeOfPart;
end;
end;
end;
{ TfrmSplitter.teNumberPartsChange }
procedure TfrmSplitter.teNumberPartsChange(Sender: TObject);
begin
if teNumberParts.Focused then SetSizeOfPart; //Do the function ONLY-IF it's the result of someone typing in the field
end;
{ TfrmSplitter.FormCloseQuery }
procedure TfrmSplitter.FormCloseQuery(Sender: TObject; var CanClose: boolean);
var
isTooManyFiles: boolean;
begin
if (ModalResult <> mrCancel) then
begin
if cmbxSize.ItemIndex <> 0 then
iVolumeSize:= StrConvert(cmbxSize.Text)
else begin
iVolumeSize:= 0;
end;
if (iVolumeSize <= 0) AND (cmbxSize.ItemIndex<>0) then
begin
ShowMessageBox(rsSplitErrFileSize, rsSimpleWordError+'!', MB_OK or MB_ICONERROR); //Incorrect file size format! (Used "ShowMessageBox" instead of "MsgError" 'cause with "MsgError", user can still click on the frmSplitter form and type in it).
end
else
begin
if not mbForceDirectory(IncludeTrailingPathDelimiter(mbExpandFileName(edDirTarget.Text))) then
begin
ShowMessageBox(rsSplitErrDirectory, rsSimpleWordError+'!', MB_OK or MB_ICONERROR); //Unable to create target directory!
end
else
begin
if teNumberParts.Text <> rsMSgUndeterminedNumberOfFile then iVolumeNumber := StrToInt(teNumberParts.Text) else iVolumeNumber := 0;
if (iVolumeNumber < 1) AND (teNumberParts.Text <> rsMSgUndeterminedNumberOfFile) then
begin
ShowMessageBox(rsSplitErrSplitFile, rsSimpleWordError+'!', MB_OK or MB_ICONERROR); //Unable to split the file!
end
else
begin
isTooManyFiles:=(iVolumeNumber > 100);
if isTooManyFiles then isTooManyFiles:=(MessageDlg(Caption, rsSplitMsgManyParts, mtWarning, mbYesNo, 0) <> mrYes);
if not isTooManyFiles then
begin
MyModalResult:= mrOk;
end; //if isTooManyFiles then
end; //if (iVolumeNumber = 0) then
end; //if not mbForceDirectory(edDirTarget.Text) then
end; //if iVolumeSize <= 0 then
CanClose:= (MyModalResult = mrOK);
end;
ModalResult := MyModalResult;
// Don't save properties when cancel operation
if ModalResult = mrCancel then SessionProperties:= EmptyStr;
end;
end.