![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Запись и чтение строк в FileStream
Слава-17 © (2007-07-03 18:04:00)При создании "псевдоархивера" возникла трудность с записью имени файлов при помощи TFileStream.Write. Вот код:
var
Stream: TFileStream;
SR: TSearchRec;
Path: String;
FileSize: Integer;
FileName: String;
begin
Path := 'E:\Temp\TestPack\';
with TFileStream.Create('test1', fmCreate) do try
if FindFirst(Path + '*.*', faHidden or faSysFile, SR) = 0 then try
repeat
Stream := TFileStream.Create(Path + SR.Name, fmOpenRead);
try
FileSize := Stream.Size;
FileName := SR.Name;
Write(FileSize, SizeOf(FileSize));
Write(FileName, SizeOf(FileName));
CopyFrom(Stream, FileSize);
finally
Stream.Free;
end;
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
finally
Free;
end;
end;
При исполнении строки Write(FileName, SizeOf(FileName)) что-то записывается, но только не имя файла, так как длина записываемых данных намного меньше длины имени файла. Попытки "распаковать" такой псевдоархив ни к чему не приводят.
Кажется, я догадываюсь, в чем проблема -- имя файла пишется не полностью, так как SizeOf(FileName) возвращает размер переменной.
Если писать Write(FileName, Length(FileName)), имена записываются нормально. Только как читать их? Ведь длина имени каждого файла различна!
Извиняюсь за столь сумбурное сообщение. Чего-то я запутался. :-/
Слава-17 (2007-07-07 10:41:00) [1]Огромное вам спасибо! Наконец-таки!! :) Думаю, что после этого реализовать чтение самих файлов из "архива" особого труда не составит...
Как ра таки по чтению у меня и вопрос. Данные о файлах, находящихся в архиве, хранятся в массиве. Как я понимаю, чтобы считать нужный файл, надо создать подобную процедуру:
procedure Чтение_файла_из_архива(Имя_Файла: Integer);
var
i: Integer;
begin
for i := 0 to FilesCount - 1 do
if Files[i].FileName = Имя_файла then begin
Считать_данные
end;
end;
Но ведь поиск в массиве может занимать долгое время! если файлов десять, то это будет и незаметно... А если десять тысяч? Или я ошибаюсь?
{RASkov} (2007-07-06 22:23:00) [2]> [37] Слава-17 (06.07.07 21:36)
Вот держи... я немного изменил, думаю найдешь и разберешься, что к чему...
Так же возможно придется заменить некоторые компоненты и uses соответственно.
В кратце: на форме Мемо, пару кнопок, и, грубо, два Едита....
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Mask, ToolEdit;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
DirPath: TDirectoryEdit;
FilePath: TFilenameEdit;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TFileRecord = record
SizeOfFile: Integer;
LengthName: Integer;
FileName: String;
FileOffset: Integer;
end;
TFileRecords = array of TFileRecord;
function CreateArchiveHeader(Path, Mask: String): TFileRecords;
var
SR: TSearchRec;
i: Integer;
begin
i := 0;
if FindFirst(IncludeTrailingPathDelimiter(Path) + Mask, faAnyFile, SR) = 0 then try
repeat
SetLength(Result, i + 1);
Result[i].SizeOfFile := SR.Size;
if i = 0 then
Result[i].FileOffset := 0
else
Result[i].FileOffset := Result[i - 1].FileOffset + Result[i - 1].SizeOfFile;
Result[i].LengthName := Length(SR.Name);
Result[i].FileName := SR.Name;
Inc(i);
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Files: TFileRecords;
i, j: Integer;
S: String;
begin
Files := CreateArchiveHeader(DirPath.Text, '*.*');
with TFileStream.Create('C:\test.ebook', fmCreate) do try
i := Size;
j := Length(Files);
WriteBuffer(i, SizeOf(Size));
WriteBuffer(j, SizeOf(Integer));
Memo1.Lines.Add('Сохранение ['+IntToStr(j)+'] файла(ов)');
for i := 0 to j-1 do begin
WriteBuffer(Files[i].SizeOfFile, SizeOf(Files[i].SizeOfFile));
WriteBuffer(Files[i].FileOffset, SizeOf(Files[i].FileOffset));
WriteBuffer(Files[i].LengthName, SizeOf(Files[i].LengthName));
WriteBuffer(Files[i].FileName[1], Files[i].LengthName);
S:=Format('%-40s %-10d %-10d', [Files[i].FileName, Files[i].SizeOfFile, Files[i].FileOffset]);
Memo1.Lines.Add(S);
end;
i := Size;
Memo1.Lines.Add('Размер заголовка: '+IntToStr(Size)+' байт');
Memo1.Lines.Add(StringOfChar('-', 100));
Seek(0, soFromBeginning);
WriteBuffer(i, SizeOf(i));
finally
Free;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
Files: TFileRecords;
i, FilesCount, HeadSize: Integer;
S: String;
begin
with TFileStream.Create(FilePath.Text, fmOpenRead) do try
Memo1.Lines.Add('Чтение файла: '+FilePath.Text);
ReadBuffer(HeadSize, SizeOf(Size));
ReadBuffer(FilesCount, SizeOf(Integer));
SetLength(Files, FilesCount);
Memo1.Lines.Add(Format('Файлов: %d, Размер заголовка: %d', [FilesCount, HeadSize]));
for i := 0 to FilesCount-1 do begin
ReadBuffer(Files[i].SizeOfFile, SizeOf(Files[i].SizeOfFile));
ReadBuffer(Files[i].FileOffset, SizeOf(Files[i].FileOffset));
ReadBuffer(Files[i].LengthName, SizeOf(Files[i].LengthName));
SetLength(Files[i].FileName, Files[i].LengthName);
ReadBuffer(Files[i].FileName[1], Files[i].LengthName);
S:=Format('%-40s %-10d %-10d', [Files[i].FileName, Files[i].SizeOfFile, Files[i].FileOffset]);
Memo1.Lines.Add(S);
end;
Memo1.Lines.Add(StringOfChar('-', 100));
finally
Free;
end;
end;
end.
Слава-17 (2007-07-06 21:36:00) [3]
> Чему равен Х? Ясен перец, что это не верно синтаксически,
> но смысл.... уловил? :)
Так я пробовал писать SetLength(Files, FilesCount + 1), и ничего...
{RASkov} (2007-07-06 21:18:00) [4]> SizeOfName: Integer;
Ну исправь на LengthName: Integer;
Самому же проще будет разбираться в исходнике....
{RASkov} (2007-07-06 21:15:00) [5]Вообщем [34] и возможно [20] про строки....
{RASkov} (2007-07-06 21:12:00) [6]> Так ведь Length(Files) - 1 и FilesCount -- это одно и то же!
Смотри сам N:=Length-1
X:=SetLength(N);
Чему равен Х? Ясен перец, что это не верно синтаксически, но смысл.... уловил? :)
Слава-17 (2007-07-06 21:06:00) [7]Исправил я, называется. Теперь Stream Read Error и при записи. Голова у меня уже не работает. :) Выкладываю то, что просили:
type
TFileRecord = record
SizeOfFile: Integer;
SizeOfName: Integer;
FileName: String;
FileOffset: Integer;
end;
TFileRecords = array of TFileRecord;
procedure TMainForm.Button1Click(Sender: TObject);
var
Files: TFileRecords;
i, j: Integer;
begin
i := 0;
Files := CreateArchiveHeader(DirPath.Text, '*.*');
with TFileStream.Create('test.ebook', fmCreate) do try
j := Length(Files) - 1;
WriteBuffer(j, SizeOf(Integer));
WriteBuffer(i, SizeOf(Integer));
for i := 0 to j do begin
WriteBuffer(Files[i].SizeOfFile, SizeOf(Files[i].SizeOfFile));
WriteBuffer(Files[i].SizeOfName, Length(Files[i].FileName));
WriteBuffer(Pointer(Files[i].FileName)^, Files[i].SizeOfName);
WriteBuffer(Files[i].FileOffset, SizeOf(Files[i].FileOffset));
end;
i := Size;
Seek(SizeOf(Integer), soFromBeginning);
WriteBuffer(i, SizeOf(Integer));
finally
Free;
end;
end;
procedure TMainForm.Button2Click(Sender: TObject);
var
Files: TFileRecords;
ReportFile: TextFile;
i, FilesCount, StreamSize: Integer;
begin
with TFileStream.Create(FilePath.Text, fmOpenRead) do try
ReadBuffer(FilesCount, SizeOf(Integer));
SetLength(Files, FilesCount);
ReadBuffer(StreamSize, SizeOf(Integer));
for i := 0 to FilesCount do begin
ReadBuffer(Files[i].SizeOfFile, SizeOf(Files[i].SizeOfFile));
ReadBuffer(Files[i].SizeOfName, Length(Files[i].FileName));
ReadBuffer(Pointer(Files[i].FileName)^, Files[i].SizeOfName);
ReadBuffer(Files[i].FileOffset, SizeOf(Files[i].FileOffset));
end;
finally
Free;
end;
> Ты пишешь:
> А читаешь и выставляешь:
Так ведь Length(Files) - 1 и FilesCount -- это одно и то же!
{RASkov} (2007-07-06 20:49:00) [8]Ты пишешь:
> j := Length(Files) - 1;
А читаешь и выставляешь:
> ReadBuffer(FilesCount, SizeOf(Integer));
> SetLength(Files, FilesCount);
{RASkov} (2007-07-06 20:46:00) [9]> [30] Слава-17 (06.07.07 20:40)
Ну кидай исправленные
procedure TMainForm.Button1Click(Sender: TObject);
procedure TMainForm.Button2Click(Sender: TObject);
и описание TFileRecord...
Слава-17 (2007-07-06 20:40:00) [10]Все равно... :(( Шо за дела??
{RASkov} (2007-07-06 19:49:00) [11]> ReadBuffer(Files[i].SizeOfFile, SizeOf( Integer));
Не принципиально, но лучше так
ReadBuffer(Files[i].SizeOfFile, SizeOf( Files[i].SizeOfFile));
А то сменишь тип Files.SizeOfFile на Byte, например, в описании типа TFileRecord, и будешь потом долго искать проблему....
Это и к записи(WriteBuffer) относиться...
{RASkov} (2007-07-06 19:44:00) [12]> [26] Слава-17 (06.07.07 18:44)
Ну конечно же
> WriteBuffer(Files[i].SizeOfName, SizeOf(Integer));
> WriteBuffer(Pointer(Files[i].FileName)^, Files[i].SizeOfName);
И опять имя с толку сводит... вроде как должно не SizeOfName а LengthOfName
WriteBuffer(Files[i].SizeOfName, Length(Files[i].FileName));
{RASkov} (2007-07-06 19:39:00) [13]> [26] Слава-17 (06.07.07 18:44)
Попробуй строки писать/читать как в [20]...
Слава-17 (2007-07-06 18:44:00) [14]И опять выскакивает Stream Read Error... :-/
{RASkov} (2007-07-06 16:21:00) [15]>
> WriteBuffer(j, SizeOf(Integer));
> Position := Position + 4;
> for i := 0 to j do begin
WriteBuffer(j, SizeOf(Integer));
WriteBuffer(0, SizeOf(Integer));
for i := 0 to j do begin
......
i := Size;
Seek(SizeOf(Integer), soFromBeginning);
WriteBuffer(i, SizeOf(Integer));
И на чтении
> i, FilesCount, StreamSize: Integer;
Я думаю лучше будет HeadSize смотреться...:)
Слава-17 (2007-07-06 12:14:00) [16]И опять ошибка при чтении. Наверное, я никогда от вас не отстану. :)
function CreateArchiveHeader(Path, Mask: String): TFileRecords;
var
SR: TSearchRec;
i: Integer;
begin
i := 0;
if FindFirst(Path + Mask, faHidden or faSysFile, SR) = 0 then try
repeat
SetLength(Result, i + 1);
Result[i].SizeOfFile := SR.Size;
if i = 0 then
Result[i].FileOffset := 0
else
Result[i].FileOffset := Result[i - 1].FileOffset + Result[i - 1].SizeOfFile;
Result[i].SizeOfName := Length(SR.Name);
Result[i].FileName := SR.Name;
Inc(i);
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;
procedure TMainForm.Button1Click(Sender: TObject);
var
Files: TFileRecords;
i, j: Integer;
begin
Files := CreateArchiveHeader(DirPath.Text, '*.*');
with TFileStream.Create('test', fmCreate) do try
j := Length(Files) - 1;
WriteBuffer(j, SizeOf(Integer));
Position := Position + 4;
for i := 0 to j do begin
WriteBuffer(Files[i].SizeOfFile, SizeOf(Integer));
WriteBuffer(Files[i].SizeOfName, SizeOf(Integer));
WriteBuffer(Pointer(Files[i].FileName)^, Files[i].SizeOfName);
WriteBuffer(Files[i].FileOffset, SizeOf(integer));
end;
i := Position;
Seek(4, soFromBeginning);
WriteBuffer(i, SizeOf(Integer));
finally
Free;
end;
end;
procedure TMainForm.Button2Click(Sender: TObject);
var
Files: TFileRecords;
ReportFile: TextFile;
i, FilesCount, StreamSize: Integer;
begin
with TFileStream.Create(FilePath.Text, fmOpenRead) do try
ReadBuffer(FilesCount, SizeOf(Integer));
SetLength(Files, FilesCount);
ReadBuffer(StreamSize, SizeOf(Integer));
for i := 0 to FilesCount do begin
ReadBuffer(Files[i].SizeOfFile, SizeOf(Integer));
ReadBuffer(Files[i].SizeOfName, SizeOf(Integer));
ReadBuffer(Pointer(Files[i].FileName)^, Files[i].SizeOfName);
ReadBuffer(Files[i].FileOffset, SizeOf(integer));
end;
finally
Free;
end;
end;
FilesCount и StreamSize считываются нормально. При чтении информации о файлах выдает Stream Read Error. У меня такое подозрение, что я где-то напутал со счетчиками цикла. Только вот где?.. :-/
Слава-17 (2007-07-05 20:26:00) [17]
> Ну вопервых... немного не так... сам подумай, у тебя ж после
> заголовка идут сами файлы(если заголовок вначале "архива")
Мне хотя бы организовать запись и чтение заголовка. :) А с файлами буду возиться уже после этого...
> И еще.... намешал в кучу и текстовый файл и "потоковый".
> .. тогда лучше без with и сразу оба обрабатывать(своими
> переменными)... имхо.
Вывод в текстовый файл -- это для проверки. Как будет все нормально читаться, уберу его.
> Кстати... твой "архив" это нечто склейки файлов...Можно
> организовать и так:Тупо сливаешь все файлы в один файл(т.
> е. пишешь файлы один за другим) при этом наполняешь "заголовок",
> а сам заголовок пишешь в другой файл.Получиться:FileName.
> myarcFileName.headОстальное все также, только в итоге два
> файла (свои плюсы... свои минусы).Опять же говорю - все
> мои посты в этой ветке - это только мысли вслух :)
Нет... :) Мой "архив" будет приклеен к exe-файлу в качестве оверлея, так что это не подходит. Делаю не SFX.
Ладно, еще раз всем спасибо. Попробую. Отпишусь о результатах скорее всего завтра.
{RASkov} (2007-07-05 19:01:00) [18]Кстати... твой "архив" это нечто склейки файлов...
Можно организовать и так:
Тупо сливаешь все файлы в один файл(т.е. пишешь файлы один за другим) при этом наполняешь "заголовок", а сам заголовок пишешь в другой файл.
Получиться:
FileName.myarc
FileName.head
Остальное все также, только в итоге два файла (свои плюсы... свои минусы).
Опять же говорю - все мои посты в этой ветке - это только мысли вслух :)
{RASkov} (2007-07-05 18:51:00) [19]> И еще.... намешал в кучу и текстовый файл и "потоковый"...
> тогда лучше без with и сразу оба обрабатывать(своими переменными)
> ... имхо.
Процедуры Чтения и Записи практически не должны отличаться...
Вот на основе твоей процедуры чтения - сделай процедуру записи с некоторыми поправками, например из [20]...
Да... наполнение массива TFileRecord я бы вынес в отдельный(свой) для этого метод...
Тогда именно то получиться, что я тебе и говорю в этом посте.....
В итоге, нечто такое получается:
type TFileRecords = array of TFileRecord;
.....
function CreateHeadFileArch(const Path, Mask: String): TFileRecords;
procedure SaveFileArch(const FlNm: String);
procedure LoadFileArch(const FlNm: String);
{RASkov} (2007-07-05 18:40:00) [20]> [19] Слава-17 (05.07.07 18:03)
> procedure Чтение_файловой_таблицы;
.....
> repeat
> SetLength(Files, i+1);
> Stream.Read(Files[i].SizeOfFile, SizeOf(Integer));
> Stream.Read(Files[i].FileOffset, SizeOf(Integer));
> Stream.Read(Files[i].SizeOfName, SizeOf(Integer));
> Stream.Read(Pointer(Files[i].FileName)^, Files[i].SizeOfName);
> WriteLn(ReportFile, Files[i].SizeOfFile, ' ', Files[i].FileOffset, ' ', Files[i].SizeOfName, ' ', Files[i].FileName);
> Inc(i);
> until Stream.Position = Stream.Size;
Ну вопервых... немного не так... сам подумай, у тебя ж после заголовка идут сами файлы(если заголовок вначале "архива") См[11]
При чтении один раз делать SetLength(Files, n); и дальше цикл for
Во вторых я бы делал запись строк так:
var N: Integer;
.....
N:=Length(Files[i].FileName);
Stream.WriteBuffer(N, SizeOf(N));
Stream.WriteBuffer(Files[i].FileName[1], N);
...
Соответственно чтение так:
var N: Integer;
.....
Stream.ReadBuffer(N, SizeOf(N));
SetLength(Files[i].FileName, N);
Stream.ReadBuffer(Files[i].FileName[1], N);
....
И не забываем про то, что в начало "архива" пишем кол-во элементов TFileRecord и весь размер заголовка, который после записи заголовка равен Stream.Size
И еще.... намешал в кучу и текстовый файл и "потоковый"... тогда лучше без with и сразу оба обрабатывать(своими переменными)... имхо.