Копирование методами Windows.

uses ShellApi;

function WindowsCopyFile(FromFile, ToDir : string) : boolean;
var F : TShFileOpStruct;
begin
F.Wnd := 0; F.wFunc := FO_COPY;
FromFile:=FromFile+#0; F.pFrom:=pchar(FromFile);
ToDir:=ToDir+#0; F.pTo:=pchar(ToDir);
F.fFlags := FOF_ALLOWUNDO or FOF_NOCONFIRMATION;
result:=ShFileOperation(F) = 0;
end;

// Пример копирования:
procedure TForm1.Button1Click(Sender: TObject);
begin
if not WindowsCopyFile(’C:\UTIL\ARJ.EXE’, GetCurrentDir) then
ShowMessage(’Copy Failed’);
end;

Июнь 19, 2008 — Рубрика: Delphi
Метки: , ,

Копирование методом LZExpand.

uses LZExpand;

procedure CopyFile(FromFileName, ToFileName : string);
var
FromFile, ToFile: File;
begin
AssignFile(FromFile, FromFileName);
AssignFile(ToFile, ToFileName);
Reset(FromFile);
try
Rewrite(ToFile);
try
if LZCopy(TFileRec(FromFile).Handle, TFileRec(ToFile).Handle)<0 then
raise Exception.Create(’Error using LZCopy’)
finally
CloseFile(ToFile);
end;
finally
CloseFile(FromFile);
end;
end;

Июнь 5, 2008 — Рубрика: Delphi
Метки: ,

Копирование методом Pascal.

type
TCallBack=procedure (Position,Size:Longint); {Для индикации процесса копирования}

procedure FastFileCopy(Const InfileName, OutFileName: String; CallBack: TCallBack);
Const BufSize = 3*4*4096; { 48Kbytes дает прекрасный результат }
type
PBuffer = ^TBuffer;
TBuffer = array [1..BufSize] of Byte;
var
Size : integer;
Buffer : PBuffer;
infile, outfile : File;
SizeDone,SizeFile: Longint;
begin
if (InFileName <> OutFileName) then
begin
buffer := Nil;
AssignFile(infile, InFileName);
System.Reset(infile, 1);
try
SizeFile := FileSize(infile);
AssignFile(outfile, OutFileName);
System.Rewrite(outfile, 1);
try
SizeDone := 0; New(Buffer);
repeat
BlockRead(infile, Buffer^, BufSize, Size);
Inc(SizeDone, Size);
CallBack(SizeDone, SizeFile);
BlockWrite(outfile,Buffer^, Size)
until Size < BufSize;
FileSetDate(TFileRec(outfile).Handle,
FileGetDate(TFileRec(infile).Handle));
finally
if Buffer <> Nil then Dispose(Buffer);
System.close(outfile)
end;
finally
System.close(infile);
end;
end else
Raise EInOutError.Create(’File cannot be copied into itself’);
end;

Июнь 1, 2008 — Рубрика: Delphi
Метки: ,

Копирование методом потока.

procedure FileCopy(Const SourceFileName, TargetFileName: String);
var
S,T : TFileStream;
begin
S := TFileStream.Create(sourcefilename, fmOpenRead );
try
T := TFileStream.Create(targetfilename, fmOpenWrite or fmCreate);
try
T.CopyFrom(S, S.Size ) ;
FileSetDate(T.Handle, FileGetDate(S.Handle));
finally
T.Free;
end;
finally
S.Free;
end;
end;

Май 3, 2008 — Рубрика: Delphi
Метки: , ,

Копирование (продолжение)

Существует такая штука, как сжатие данных. Хорошо, если вы инсталлируете свою программу с компакт-диска. Как правило, в такой ситуации вы не знаете, что ещё записать на эту бездонную болванку и несколько лишних мегабайт вам в данной ситуации совсем не помешают :)

Совершенно по другому обстоят дела с дискетами. На дискеты надо помещать сжатую информацию — в этом случае вся ваша программа возможно (только возможно) влезет всего лишь на тридцать четыре дискеты. Вот как делать сжатие — это вопрос.

Я расскажу вам о нескольких методах сжатия данных, которыми вы можете воспользоваться.

Метод номер раз — это сжатие файлов стандартными утилитами, разработанными фирмой Microsoft. Когда-то это была программа compress.exe, сейчас — cabarc.exe.
hInFile := LZOpenFile(PChar(SourcePath), ofInReOpenBuff, OF_READ);
hOutFile := LZOpenFile(PChar(TargetPath), ofOutReOpenBuff, OF_CREATE or OF_WRITE);
iLZError := LZCopy(hInFile, hOutFile);
if iLZError > 0 then
// Операция выполнилась успешно, скопировано iLZError байт
else
// Ошибка номер iLZError
LZClose(hOutFile);
LZClose(hInFile);

Метод номер два — сжатие файлов с помощью библиотеки ZLib, которая поставляется вместе с Delphi (она находится в каталоге \Info\Extras\ZLib оригинального диска с Delphi). Она предоставляет вам два класса, которые являются наследниками TStream. Вы можете воспользоваться кодом функции копирования при помощи TFileStream из предыдущей статьи для того, чтобы реализовать как сжатие, так и распаковку произвольного потока (в том числе и файла).

Ещё один метод — использование динамической библиотеки unrar.dll, разработанной Евгением Рошалом. Существуют и другие библиотеки (даже компоненты), вы можете свободно найти их, если будете достаточно долго шляться по Интернету.

Примечание:
Мне кажется, что менее всего размер вашей инсталляции увеличится, если вы используете методы, предлагаемые Microsoft; ровно потому, что они встроены в Windows и вам не надо записывать их на дискету.
Копирование нескольких файлов представляется достаточно простым, раз уж мы научились копировать один файл. Наиболее просто это делается, если ваши файлы поставляются в одном архиве (.CAB или .RAR). Сложным может показаться копирование файлов по маске (*.*) и копирование вложенных подкаталогов. Ниже приводится исходный текст процедуры, которая составляет список файлов в каталоге и всех вложенных подкаталогах.
procedure ReadTree(Path: String; Strings: TStrings);
procedure ReadFolder(Path: String; Strings: TStrings);
var
SearchRec: TSearchRec;
FindResult: Integer;
begin
FindResult := FindFirst(Path + ‘*.*’, faAnyFile, SearchRec);
while FindResult = 0 do
begin
// Если найден подкаталог, рекурсивно читаем его содержимое
// Не забываем игнорировать подкаталоги ‘.’ и ‘..’
with SearchRec do
if (Name <> ‘.’) and (Name <> ‘..’) then
begin
Strings.Add(Path + Name);
if (Attr and faDirectory <> 0) then
ReadFolder(Path + Name + ‘\’, Strings);
end;
FindResult := FindNext(SearchRec);
end;
FindClose(SearchRec);
end;
begin
// Эта процедура заносит в Strings список файлов во всех вложенных папках
// каталога Path и сами эти папки
Strings.Clear;
if (Length(Path) > 0) and (Path[Length(Path)] <> ‘\’) then
Path := Path + ‘\’;
ReadFolder(Path, Strings);
end;

Отдельно стоит поговорить о тех файлах, которые могут использоваться сразу несколькими программами. Для этих файлов существует даже специальное название — разделяемые (поскольку несколько программ делят их между собой). Обычно они записываются в системный каталог Windows (для Windows 98 это как правило \WINDOWS\SYSTEM, для Windows NT — \WINNT\SYSTEM32). Если системный каталог доступен только для чтения, то эти файлы необходимо записывать в каталог Windows (\WINDOWS и \WINNT соответственно), который всегда доступен для записи.
function GetSysDir: String;
var
szPath: array [0..MAX_PATH - 1] of Char;
I: Integer;
Stream: TStream;
begin
// Получаем системный каталог
GetSystemDirectory(szPath, MAX_PATH);
Result := StrPas(szPath);
// Добавляем обратный слеш в конец пути, если его там нет
if (Length(Result) > 0) and (Result[Length(Result)] <> ‘\’) then
Result := Result + ‘\’;
// Подбираем имя файла вида XXXXXXXX.TMP, где XXXXXXXX —
// шестнадцатиричное число, который не существует в системном каталоге
I := 0;
while FileExists(Result + IntToHex(I, 8) + ‘.TMP’) do
Inc(I);
try
// Создаём файл и удаляем его. Если всё нормально, то каталог доступен
// для записи.
Stream := TFileStream(Result + IntToHex(I, 8) + ‘.TMP’, fmCreate);
Stream.Free;
DeleteFile(Result + IntToHex(I, 8) + ‘.TMP’);
except
// Если создать файл не удалось, в качестве системного каталога будем
// использовать каталог Windows.
GetWindowsDirectory(szPath, MAX_PATH);
Result := StrPas(szPath);
if (Length(Result) > 0) and (Result[Length(Result)] <> ‘\’) then
Result := Result + ‘\’;
end;
end;

Если разделяемый файл уже существует в целевом каталоге, то необходимо сравнить версии, языки и др. характеристики двух файлов и на основании этого сравнения решать — копировать файл или не надо.

При копировании разделяемых файлов требуется уведомить Windows о том, что одним разделением стало больше. Это делается через реестр. При замещении файлов, которые в момент инсталляции используются Windows требуется определённая техника, поскольку перезаписать занятый файл нельзя. Эти вопросы в ближайшее время будут освещены в следующей статье. Поистине, копирование файлов — тема неисчерпаемая :)

Напоследок, исследуем вопрос о том, куда копировать файлы? В соответствии с рекомендациями Microsoft, каталог вашей программы должен иметь форму \<Название вашей фирмы>\<Название продукта>. Вы можете также включить в название каталога информацию о версии продукта, например C:\Program Files\Borland\Delphi 7, хотя возможен и вариант C:\Program Files\Borland\Delphi\7 (реально фирма Borland использует первый вариант). Бывают и исключения, в частности FAR Евгения Рошала ставится в C:\Program Files\FAR.

Разделяемые файлы следует копировать в системный каталог Windows, а если он защищён от записи — в каталог Windows.

Дальше мы пойдём по реестру…

Ноябрь 6, 2007 — Рубрика: Delphi
Метки: , ,

Копирование

Нет другого такого процесса в программе инсталляции, который бы выглядел снаружи столь простым и был бы в реализации столь сложным, как копирование. Сложная фраза. В переводе на русский язык она означает, что сделать хорошую операцию копирования файлов нелегко :)

Почему?

Потому что операция копирования файлов должна быть идеальной.

Начнём с самого просто случая — копирования одного файла. Для того, чтобы скопировать один файл, вы можете вызвать функцию Windows, которая называется CopyFile:

function CopyFile(lpExistingFileName, lpNewFileName: PChar; bFailIfExists: BOOL): BOOL; stdcall;

Как видим, функции необходимо передать имена двух файлов: исходного и целевого. Третий параметр отвечает за то, как функция будет поступать, если целевой файл уже существует. Значение True говорит о том, что функция не будет копировать файл, значение False — о том, что целевой файл будет перезаписан.

Функция возвращает True, если операция копирования была успешно выполнена. Значение False подсказывает нам, что необходимо вызвать функцию GetLastError для того, чтобы узнать код произошедшей ошибки.

Эта функция работает очень надёжно, поскольку она является частью операционной системы и практически наверняка именно она тестировалась огромное количетво раз. Тем не менее, первое, на чём мы остановимся — что в этой функции нас не устраивает?

if CopyFile(PChar(SourcePath), PChar(TargetPath), False) then
// Выполнилась успешно.
else
// Ошибка. Код ошибки можно получить, вызвав GetLastError.

У этой функции один недостаток, но он способен перекрыть все её достоинства. Мы не имеем доступа к процессу копирования. Это означает, что мы не можем показывать индикатор процесса копирования и не можем прервать функцию CopyFile, если пользователь нажал кнопку “Отмена” или клавишу Escape.

Насколько это важно? На этот вопрос вы вольны отвечать самостоятельно. Если вам потребуется копировать большое количество маленьких файлов, то для вас этот недостаток неважен. Если вы собираетесь копировать файлы размером в несколько мегабайт, то у пользователя вашей программы могут возникнуть определённые проблемы.

В Windows NT 4.0 появилась новая функция — CopyFileEx, которая позволяет снять все появившиеся проблемы, но добавляет ещё одну — она не работает в предыдущих версиях Windows :)

В Delphi для копирования файлов, мы можем воспользоваться объектами класса TFileStream. Например, так:

procedure CopyFile(Source, Target: String);
var
SourceStream: TFileStream;
TargetStream: TFileStream;
begin
SourceStream := TFileStream(Source, fmOpenRead);
try
TargetStream := TFileStream(Target, fmCreate);
try
TargetStream.CopyFrom(SourceStream, 0);
FileSetDate(TargetStream.Handle, FileGetDate(SourceStream.Handle));
finally
TargetStream.Free;
end;
finally
SouceStream.Free;
end;
FileSetAttr(Target, FileGetAttr(Source));
end;

Обработку ошибок, как говорится в таких случаях, я оставляю читателю в качестве упражнения… :)

Немного поговорим о приведённой процедуре. Саму операцию копирования выполняет метод CopyFrom. Если второй параметр этого метода равен нулю, то копируется сразу весь файл, в ином случае копируется указанное количество байтов. Мы можем копировать файл блоками, со всеми вытекающими отсюда преимуществами:

. . .
const
BlockSize = 65536;
. . .
var
ElapsedSize: Integer;
CopySize: Integer;
. . .
ElapsedSize := SourceStream.Size - SourceStream.Position;
while ElapsedSize > 0 do
begin
if ElapsedSize < BlockSize then
CopySize := ElapsedSize
else
CopySize := BlockSize;
TargetSource.CopyFrom(SourceSize, CopySize);
ElapsedSize := SourceStream.Size - SourceStream.Position;
// Здесь какие-то действия, например, модификация индикатора процесса
end;
. . .

Замечательные функции FileGetDate, FileSetDate, FileGetAttr, FileSetAttr выполняют очень важную работу, про которую не надо забывать: копируют дату создания файла и его атрибуты. Сейчас мы не будем углубляться в особенности файловой системы NTFS, в которой есть дата последней модификации файла и расширенные атрибуты, поскольку приведённого кода нам вполне для наших целей хватит.
Примечание:
Корректное копирование предполагает, что у целевого файла обязательно устанавливается флаг Archive. Я не буду углуляться в то, зачем это делается, тем более, что в нашем случае этого делать не обязательно.

Тем не менее, если вы хотите реализовать полноценную операцию копирования, не забудьте про эту маленькую особенность.
В следующей статье мы поговорим о сжатии…

Ноябрь 5, 2007 — Рубрика: Delphi
Метки: , , , , ,