Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Запись и чтение строк в FileStream


Страницы:   1 2

Слава-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 и сразу оба обрабатывать(своими переменными)... имхо.


Страницы:   1 2

Наверх

Титульная страница Поиск, карта сайта Написать письмо