Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Сохранение нескольких файлов в одном


Автор: pushok 13.8.2009, 10:39
Всем привет.

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

Первым делом думал создавать новую папку и сохранять нужные файлы в ней, но для конечного пользователя это всеравно будет неудобно.
Еще есть мысль сохранять всю нужную мне информацию в одном файле, записывая его по-байтам в определенном формате, но это как вы сами понимаете - гемор = )

Подскажите, может быть в Delphi есть какие-то предусмотренные способы или функции решения этой проблемы. Может быть в Delphi есть функции работы с архивами. Может быть эти проблемы решаются средствами WinAPI.

Заранее спасибо за помощь.

Автор: AntonN 13.8.2009, 10:50
Цитата

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


ничуть, именно так поступаю когда нужно упаковать скины для программ или что то подобное.
Если потратишь немного времени и разберешься - все будет легко и красиво smile
дял примера небольшой класс который сохраняет несколько Tbitmap (разных размеров, битности) в один файл - http://desksoft.ru/index.php?forum=13&th=110
если приглядеться то битамп перед сохранением в общий файл выгружается в Tstream - _MBit, ну так вот грузи в этот поток все что тебе угодно, хоть EXE, хоть GIF. Только придется в заголовки добавить поле типа string куда запишется название файла smile

Автор: Yanis 13.8.2009, 11:01
Товарищи велосипедисты, с исследовательской точки зрения можно, конечно, хранить вообще все файлы хоть в исполняемом файле. С практической же стороны, архивы ZIP очень даже доставляют в случае хранения ресурсов в одном хранилище. И да, pushok, в Delphi есть модуль для поддержки сжатия по алгоритму DEFLATE. Модуль http://ru.wikipedia.org/wiki/Zlib.

Добавлено @ 11:02
http://forum.vingrad.ru/index.php?act=Search&CODE=show&searchid=86d705afc119e69a36834577f5e07488&search_in=posts&result_type=topics&flag=search&debug=&highlite=zlib&skipped=

Автор: former 13.8.2009, 13:13
http://forum.vingrad.ru/forum/topic-90023.html# еще вариант.

Автор: kemiisto 13.8.2009, 13:20
Yanissmile 

http://www.delphizip.org/ есть толковый компонент для работы с Zip.

Как альтернатива - компонент с говорящим названием http://www.torry.net/authorsmore.php?id=5967. smile 

Автор: w03zd8rc 13.8.2009, 13:43
собстна можно взять консольную версию винрара (ставится вместе с винраром) и паковать нужные файлы в один архив через консоль (совсем несложно в делфи)

Автор: pushok 16.8.2009, 12:38
Ребят, пару дней разбирался - ничего не понял.

Вот из какой-то ссылки (что вы давали) вытянул кусок кода по-проще. Работает хорошо, только вот понять как он работает немогу.
С потоками в плохих отношениях = )

Напишите пожалуйста пояснения к некоторым строчкам.

Код

procedure SaveData(const s: String; BinData: TStream; FilePath: String);
var
  FS: TFileStream;
  Len: Integer;
  Len64: Int64;
begin
  FS := TFileStream.Create(FilePath,fmCreate);
  try
    Len := Length(s);
    FS.Write(Len,SizeOf(Integer));   // Вот здесь. Функция Write записывает элемент Len в поток, при этом длина в байтах этого элемента = SizeOf(Integer). Верно??
    FS.Write(s[1],Len);
    Len64 := BinData.Size;
    FS.Write(Len64,SizeOf(Int64));
    FS.CopyFrom(BinData,Len64);   // Здесь в поток FS копируется поток BinData. При этом он добавляется в поток FS ?
  finally
    FS.Free;
  end;
end;

procedure ReadData(var s: String; BinData: TStream; FilePath: String);
var
  FS: TFileStream;
  Len: Integer;
  Len64: Int64;
begin
  FS := TFileStream.Create(FilePath,fmOpenRead);
  try
    FS.Read(Len,SizeOf(Integer));
    SetLength(s,Len);
    FS.Read(s[1],Len);   // Функция Read считывает из потока FS в переменную s[1] элемент длиною в Len байт?? Или он начинает считывать с Len-ого байта все последующие? При этом этот элемент (последовательность байт) удаляется из потока или поток не изменяется?
    FS.Read(Len64,SizeOf(Int64));
    BinData.CopyFrom(FS,Len64);
  finally
    FS.Free;
  end;
end;


И еще алгоритм записи и чтения в один поток двух файлов выглядит так:
Запись
1. Записываем длину первого файла
2. Записываем первый файл
3. Записываем длину второго файла
4. Записываем второй файл

Чтение
1. Считываем длину первого файла
2. Считываем в переменную количество байт равных длине первого файла
3. Считываем длину второго файла
2. Считываем в переменную количество байт равных длине второго файла

Верно?? Если нет, опишите пожалуйста, что-то непонимаю.

Автор: MetalFan 16.8.2009, 13:40
pushok, ну да, вроде бы все написано верно. мыслишь правильно. какие-то проблемы с приведенным кодом?

Но видимо у тебя недопонимание работы потоков данных (TStream и его наследников).

Цитата(pushok @  16.8.2009,  12:38 Найти цитируемый пост)
FS.Write(Len,SizeOf(Integer));   // Вот здесь. Функция Write записывает элемент Len в поток, при этом длина в байтах этого элемента = SizeOf(Integer). Верно??

верно.

Добавлено @ 13:47
Цитата(pushok @  16.8.2009,  12:38 Найти цитируемый пост)
FS.CopyFrom(BinData,Len64);   // Здесь в поток FS копируется поток BinData. При этом он добавляется в поток FS ?

хм... странное понимание... здесь в поток FS копируется Len64 байт из потока BinData, при чем  данные в потоке BinData остаются неизменными.

Цитата(pushok @  16.8.2009,  12:38 Найти цитируемый пост)
 FS.Read(s[1],Len);   // Функция Read считывает из потока FS в переменную s[1] элемент длиною в Len байт?? Или он начинает считывать с Len-ого байта все последующие? При этом этот элемент (последовательность байт) удаляется из потока или поток не изменяется?

Да, здесь функция Read читает Len байт по адресу переменной s[1] с текущей позиции(TStream.Position) потока FS. при этом позиция потока FS сдвигается на Len байт. данные в потоке FS при этом никак не изменяются.

Автор: pushok 16.8.2009, 20:54
Ок. С позицией понял. Теперь такой вопрос.

Пытаюсь записать два файла в один, а потом извлечь их.
Процедура записи:
Код

procedure TForm1.Button5Click(Sender: TObject);
var
b:Int64;
f,fs:TFileStream;
begin
fs:=TFileStream.Create('1.dat',fmCreate);
fs.Free;
fs:=TFileStream.Create('1.dat',fmOpenWrite);

f:=TFileStream.Create('ad.txt',fmOpenRead);
b:=f.Size;
fs.Write(b,SizeOf(Int64));
fs.Write(f,b);
f.Free;


f:=TFileStream.Create('ad1.bmp',fmOpenRead);
b:=f.Size;
fs.Write(b,SizeOf(Int64));
fs.Write(f,b);
f.Free;

fs.Free;
end;


Файл '1.dat' появляется но занимает только 1 Кб

Процедура чтения (пробовал также как и записи, но пишит какую-то ошибку, сделал по-другому)
Код

procedure TForm1.Button6Click(Sender: TObject);
var
fs,f:TFileStream;
ft:TMemoryStream;
b:Int64;
begin
fs:=TFileStream.Create('1.dat',fmOpenRead);

f:=TFileStream.Create('00a.txt',fmCreate);
f.Free;
f:=TFileStream.Create('00a.txt',fmOpenWrite);
fs.Read(b,SizeOf(Int64));
fs.Read(ft,b);
f.Write(ft,b);
f.Free;

f:=TFileStream.Create('00b.bmp',fmCreate);
f.Free;
f:=TFileStream.Create('00b.bmp',fmOpenWrite);
fs.Read(b,SizeOf(Int64));
fs.Read(ft,b);
f.Write(ft,b);
f.Free;

fs.Free;
end;


Два файла появляются, но занимают они по 0 Кб каждый и ничего не содержат.

В чем моя ошибка?

(при этом если я второй файл выключаю из работы, т.е. пытаюсь сохранить только один, та же фигня)

Автор: AntonN 16.8.2009, 21:17
м/у файловыми потоками сделай fs.CopyFrom(f,fs.Size); но предварительно не забывай задавать размер dest'у и устанавливать позиции (fs.position:=0;)

Автор: kami 16.8.2009, 21:37
Для записи:
вместо 
Цитата(pushok @  16.8.2009,  20:54 Найти цитируемый пост)
fs.Write(f,b);

Код

fs.CopyFrom(f, b); // в данном случае можно и (f, 0). F1 на CopyFrom рулит.

Соответственно - для чтения тоже.

Цитата(pushok @  16.8.2009,  20:54 Найти цитируемый пост)
 пишит какую-то ошибку, сделал по-другому

Вот в этом Ваша ошибка.
Не нужно считать ошибку "какой-то". Ошибка имеет вполне конкретный смысл, конкретное значение, а при запуске под отладчиком (т.е. в Delphi ) - даже конкретную строчку, на которой происходит.
Вот это всё и нужно проанализировать, и если самому непонятна причина ошибки, то все данные по ней - вывести сюда.

Цитата(pushok @  16.8.2009,  20:54 Найти цитируемый пост)

fs.Read(ft,b);
f.Write(ft,b);

Лишние телодвижения.
нужно проще:
Код

f.CopyFrom(fs, b);

Автор: pushok 16.8.2009, 23:23
Спасибо всем = )))
все работает, вроде все ясно.

CopyFrom тоже указатель двигает, как и Read, верно?

Автор: AntonN 17.8.2009, 00:28
лучше считать что не двигает и делать все самому smile

Автор: kami 17.8.2009, 07:32
Цитата(AntonN @  17.8.2009,  00:28 Найти цитируемый пост)
лучше считать что не двигает и делать все самому

Лучше. так. не. считать.

Добавлено через 2 минуты и 28 секунд
Read и Write перемещают указатель потока.
соответственно - это делают и все их производные - Read|WriteBuffer; CopyFrom и т.д.

Добавлено через 5 минут и 53 секунды
Маленькое примечание:
если есть TFileStream, с доступом fsOpenRead и в него попытаться что-нибудь записать, то ничего записано не будет и указатель соответственно не передвинется.
Недавно потратил 20 минут на поиск этого своего глюка - не хотел файл записываться и все, размер=0 smile

Автор: AntonN 17.8.2009, 10:41
Цитата

Лучше. так. не. считать.

Почему. лучше. ?

Автор: MetalFan 17.8.2009, 14:42
Цитата(AntonN @  17.8.2009,  00:28 Найти цитируемый пост)
лучше считать что не двигает и делать все самому smile 

смотря что подразумевается под "делать все самому"...
если имеется ввиду неиспользование CopyFrom, то здесь я тоже не согласен.
ХОТЯ есть некоторые моменты, когда CopyFrom не подходит...

Автор: kami 17.8.2009, 17:08
Цитата(AntonN @  17.8.2009,  10:41 Найти цитируемый пост)
Почему. лучше. ?

smile
CopyFrom передвинет сам указатель на нужное количество байт.
В этом случае манипуляции с Seek (или оберткой над ним - Position) - потенциальный глюкодром, причем достаточно неявный.
Цитата(MetalFan @  17.8.2009,  14:42 Найти цитируемый пост)
ХОТЯ есть некоторые моменты, когда CopyFrom не подходит...

Можно пример "навскидку"? Просто я всегда, когда нужно перекинуть данные из одного TStream в другой, пользуюсь CopyFrom (а его второй параметр :=0 - это вообще песня smile ). Только в данном случае, если размер исходного файла =0, то при распаковке это приведет к багу - все последующие файлы ошибочно запишутся в этот. Но это контролируемая штука, один if - и всё в порядке.

Автор: AntonN 17.8.2009, 18:12
Цитата

В этом случае манипуляции с Seek (или оберткой над ним - Position) - потенциальный глюкодром, причем достаточно неявный.

Вот когда ты вручную указываешь позицию куда писать - ты можешь точно видеть в коде откуда он пишет.
А когда надеешься на невидимый указатель который "вроде бы должен быть тут" - вот это уже потенциальный глюкодром smile


MetalFan
Цитата

смотря что подразумевается под "делать все самому"...

подразумевается "выполнять position:=" когда точно нужно быть увереным в какую позицию должен установиться указатель.

Добавлено через 7 минут и 5 секунд
дополню на всякий случай, что опыта на стримах съел прилично, старый кусочек его вывалился в первых постах (который за час обрастает нужными полями в хедере (название файла, атрибуты и тп), прикручивается zlib и если надо шифрование).
Никакого глюкодрома за годА плотного щупанья TFileStream.position я не встречал, зато часто натыкался на свои же грабли когда указатель после Write был не там, где должен быть перед очередной операцией (в основном связано было с модификацией кода, когда подзабывалась структура файла).

Автор: kami 17.8.2009, 20:35
Цитата(AntonN @  17.8.2009,  18:12 Найти цитируемый пост)
Вот когда ты вручную указываешь позицию куда писать - ты можешь точно видеть в коде откуда он пишет.

В контексте данной задачи это (на мой взгляд) будет выглядеть примерно так:

1. установить указатель в 0.
Loop:
  2. считать длину файла (и при необходимости - имя, атрибуты и т.д.)
  3. запомнить позицию указателя
  4. считать файл из потока
  5. установить указатель на "запомненный"+длина файла
goto Loop (пока не достигнем конца потока).

Если доводить до абсурда - то каждая четная операция(2,4) тоже должна обрамляться запоминанием предыдущего положения указателя и ручным передвиганием его дальше.
Извините, но... "это не наш метод".

Цитата(AntonN @  17.8.2009,  18:12 Найти цитируемый пост)
о опыта на стримах съел прилично,

Большинство из посетителей форума могут сказать то же самое.
Цитата(AntonN @  17.8.2009,  18:12 Найти цитируемый пост)
Никакого глюкодрома за годА плотного щупанья TFileStream.position я не встречал, зато часто натыкался на свои же грабли когда указатель после Write был не там, где должен быть перед очередной операцией

... не будем начинать холивар, тем более что к теме он относится слабо smile

Добавлено через 1 минуту и 36 секунд
Цитата(AntonN @  17.8.2009,  18:12 Найти цитируемый пост)
когда надеешься на невидимый указатель который "вроде бы должен быть тут"

А именно поэтому Read и Write методы TStream - это функции. Возвращающие реальное количество считанного/записанного.

Автор: MetalFan 17.8.2009, 21:10
Цитата(AntonN @  17.8.2009,  00:28 Найти цитируемый пост)
лучше считать что не двигает и делать все самому smile


Цитата(kami @  17.8.2009,  07:32 Найти цитируемый пост)
Лучше. так. не. считать.

ребят, ну не устраивайте тут считалки. Ctrl+Click еще никто не отменял. а код CopyFrom не так уж и сложен для понимания. и сразу отпадут все вопросы, при каких входных данных этот злобный CopyFrom какие выходные данные оставит)
если кому что-то непонятно, могу здесь код CopyFrom прокомментировать.
Цитата(kami @  17.8.2009,  17:08 Найти цитируемый пост)
Можно пример "навскидку"?

да на здоровье)
единственной проблемой, с которой я столкнулся при использовании метода TStream.CopyFrom, и из-за которой от него пришлось отказаться, это была проблема, связанная с использованием ZLib.TCompressStream, ZLib.TDecompressStream...
в частности при распаковке потока следующим кодом:
Код

var
  lCompressedStream, lDecompressedStream: TStream;
  lDecompressStream: TDecompressStream;
begin
...
//где-то в коде получаем сжатые данные в lComressedStream и нам надо его распаковать в lDecompressStream
...
  lDecompressStream := TDescompressStream.Create(lCompressedStream);
  try
    lDecompressedStream.CopyFrom(lDecompressStream, 0); //тут ловим EDecompressionError
  finally
    lDecompressStream.Free;
  end;

ибо TDecompressionStream не умеет делать Seek(0, soFromEnd)... 
т.е. мы не узнаем размер распакованных данных, не распаковав их, или не сохранив из предварительно в том же потоке(к примеру).
тогда вместо CopyFrom пришлось использовать нечто такое:
Код

var
  lBuff: array [0..1023] of byte;
  lReaded: integer;
  lSrcStrm, lDstStrm: TStream;
  lDecompStrm: TDecompressionStream;
begin
...
  lDecompStrm := TDecompressionStream.Create( lSrcStrm );
  try
    repeat
      lReaded := lDecompStrm.Read(lBuff, 1024 );
      lDstStrm.Write( lBuff, lReaded);
    until lReaded > 0;
  finally
    lDecompStrm.Free;
  end;

ну вот как-то так...

Автор: kami 17.8.2009, 21:27
Цитата(MetalFan @  17.8.2009,  21:10 Найти цитируемый пост)
ибо TDecompressionStream не умеет делать Seek(0, soFromEnd)... 

Ага, точно, есть такое. Одна из причин, из-за которых и перешел на FastZLib.

Автор: MetalFan 18.8.2009, 11:25
Цитата(kami @  17.8.2009,  21:27 Найти цитируемый пост)
Одна из причин, из-за которых и перешел на FastZLib. 

а какие еще были причины? хотя это наверное уже злостный оффтоп будет)

Автор: Yanis 18.8.2009, 11:26
Цитата(MetalFan @  18.8.2009,  12:25 Найти цитируемый пост)
хотя это наверное уже злостный оффтоп будет) 

Он уже был smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)