Создаем
самораспаковывающийся ZIP архив.
Что из
себя представляет самораспаковывающийся архив? Это
файл, объединивший в себе два файла – модуль распаковки и
собственно архив.
Модуль распаковки является обычным исполняемым файлом и его можно
написать
почти на любом языке программирования, позволяющем работать с архивами
нужного
формата, в нашем случае, ZIP. Архив
в файле находится сразу же после модуля
распаковки, то есть, он дописан в конец исполняемого файла. Это конечно
же не
единственный способ создания самораспаковывающегося архива, но такой
метод
используется многими архиваторами, в частности, популярной программой WinRAR и мы не
будем изобретать колесо, а используем
уже проверенные способы.
Для
создания модуля распаковки, будем использовать
компилятор PureBasic,
бесплатную, демонстрационную версию которого, можно
скачать на официальном сайте http://purebasic.com/download.php.
Там есть разновидности для нескольких платформ, но нам нужна для Windows
x86.
Кроме того, понадобится
библиотека PureZIP,
которая позволяет работать с ZIP
архивами. Ее можно скачать на сайте
http://gnozal.ucoz.com.
К статье
приложена библиотека PureZIP для
компилятора PureBasic
версии
4.51, который использовался
при разработке рассматриваемых программ. Библиотека устанавливается с
помощью
инсталлятора, нужно лишь правильно указать папку установленной средой PureBasic.
После того как скачали и установили PureBasic и библиотеку PureZIP для него, можно приступать к кодингу. Для примера был создан относительно не сложный распаковщик архива, окно которого показано на рисунке.
Он позволяет выбрать место (папку) распаковки файлов, а так же отображает прогресс извлечения текущего файла и прогресс распаковки всего архива. Кроме того, если архив защищен паролем, то появится окно для его ввода. Мы не будем углубляться в создание GUI программы и обработчиков событий, поскольку это выходит за рамки данной статьи, и сразу рассмотрим код распаковки архива в заданную папку - процедуру ExtractArchive()
; Распаковка архива в папку, указанную в переменной ExtractPath.
FileInfo.PureZIP_FileInfo
DoneFiles = 2
If PureZIP_GetFileInfo(ProgName, 0, @FileInfo) = #True
EndIf
ReturnValue.l = PureZIP_Archive_FindFirst() ; Поиск первого файла в архиве.
If PureZIP_Archive_FileInfo(@FileInfo) = #UNZ_OK ; Данные о файле (размер, степень сжатия и др.)
Status = PureZIP_Archive_Extract(ExtractPath, #True, @BreakExtract) ; Распаковка файла на диск
EndIf
Else
MessageRequester("","Произошла ошибка при распаковке"+Chr(10)+FileInfo\FileName,#MB_ICONWARNING)
EndIf
If ReturnValue = #UNZ_END_OF_LIST_OF_FILE
SetGadgetState(#ProgressBar_File, 0)
SetGadgetState(#ProgressBar_All, 0)
SetGadgetText(#Text_ExtractFiles, "")
EndIf
Else ; Обычно свидетельствует об повреждении архива.
MessageRequester("", "Ошибка при доступе к архиву.", #MB_OK|#MB_ICONERROR)
Прежде всего, объявляются структуры и переменные, необходимые для работы с архивом. Далее, выясняется, защищен ли архив паролем. Для этого запрашивается информация об одном из файлов, который имеет нулевой индекс в архиве и анализируется состояние флагов и если нулевой бит установлен, то это признак того, что архив защищен паролем. В этом случае, при помощи функции InputRequester() будет создано диалоговое окно ввода данных, информирующее пользователя что для распаковки нужен пароль.
Введенный
пользователем пароль будет
передан функции PureZIP_SetArchivePassword(),
которая установит текущий пароль архива. После всего этого, можно
открыть архив
для чтения, что производится при помощи функции PureZIP_Archive_Read(),
единственный аргумент которой – абсолютный путь к архиву, то
есть к EXE-файлу
самораспаковывающегося
архива. Он заносится в строковую переменную ProgName
при старте
программы функцией ProgramFilename().
После
успешного получения доступа к архиву, его содержимое можно извлечь в
папку,
заданную в строковой переменной ExtractPath.
Данная операция
производится в цикле While
– Wend,
который прервется при завершении
распаковки, возникновении ошибки, либо при прерывании распаковки
пользователем.
При помощи функции PureZIP_Archive_FileInfo()
запрашивается информация о текущем файле. Это необходимо для
отображения имен
файлов извлекаемых из архива. Далее при помощи функции PureZIP_Archive_Extract()
производится извлечение текущего файла. Нужно отметить, что работа этой
функции
не завершится до тех пор, пока не будет распакован весь файл. Это
особенно
актуально для больших файлов, распаковка которых может занять
продолжительное
время. В первом аргументе функции указано место (папка), куда будет
извлечен
файл. От второго аргумента (#True или #False)
зависит, будет ли при распаковке учитываться полный
путь к файлу, сохраненный в архиве, или нет. Третий аргумент не
обязателен и
может отсутствовать. При его наличии, его значение будет передано в Calback
функцию, в которой отображается
прогресс извлечения файла из архива на диск. В рассматриваемой
программе,
передается указатель на переменную BreakExtract,
в которой
изначально хранится число 0 (#False).
Если же пользователь прервет
распаковку
файлов, то в Callback
функции, в переменную запишется число 1 (#True).
Код Callback процедуры показан ниже.
; Отображение прогресса распаковки обрабатываемого файла.
Procedure File_Progress(File.s, Percent.f, *UserParam)
Repeat
Event=WindowEvent()
; Текущее событие программы.
If Event=#PB_Event_CloseWindow ; Щелчок по крестику в заголовке окна.
Case #IDYES
End
Case #IDCANCEL
PokeL(*UserParam, #True)
EndIf
Она зарегистрирована в библиотеке PureZIP при помощи PureZIP_SetCompressionCallback() как функция отображения прогресса упаковки / распаковки файлов. Процедуре передается имя распаковываемого файла, процент его распаковки (в диапазоне 0 … 100) и данные, переданные в необязательном аргументе функции распаковки файла. В нашем случае, это указатель на локальную переменную BreakExtract процедуры ExtractArchive(). Хоть основное назначение процедуры File_Progress() состоит в отображении распаковки файла, но тот факт что при излечении файлов, она вызывается довольно часто, как минимум несколько раз в секунду, позволило возложить на нее еще и обработку событий программы на время распаковки файлов. Иначе бы пришлось распаковывать файлы в отдельном потоке. После того как при помощи функции SetGadgetState() был отображен текущий прогресс распаковки, выполняется код обработки событий программы. В цикле вызывается функция WindowEvent() до тех пор, пока не будут обработана вся очередь событий. Из всех событий, обрабатывается только одно – закрытие окна программы, при возникновении которого, на экране появится окно.
При
нажатии на кнопку "Да", работа программы завершится и ее окно
закроется. В случае нажатия на кнопку "Нет", распаковка файлов
продолжится. А если нажать на кнопку "Отмена", то распаковка файлов
прекратится, но программа продолжит свою работу.
После компиляции программы получится модуль распаковки архива, размером 90 КБ. Но архива в нем естественно нет. Его еще предстоит добавить. А как его добавить? Можно конечно заранее создать ZIP архив со всеми требуемыми файлами, а потом дописать его в конец исполняемого файла, любым доступным способом и после архива, указать число добавленных байтов. Но этот метод усложняет создание самораспаковывающегося архива, поэтому создадим небольшую программу, автоматизирующую данный процесс. Ее окно показано на скриншоте.
Программа позволяет создать самораспаковывающийся архив, защищенный паролем из файлов выбранной папки. Исполняемый файл модуля распаковки архива, хранится в секции данных программы.
; Добавление при компиляции модуля распаковки в EXE файл упаковщика.
SFX_File_End:
Он туда
помещается при помощи оператора IncludeBinary
на этапе компиляции программы.
Метки до и после этого оператора, позволяют получить данные,
загруженные
оператором IncludeBinary.
Такой способ
хранения модуля распаковки хорош тем, что программа состоит всего из
одного
исполняемого файла, а один файл хранится внутри другого.
Самораспаковывающийся архив создает процедура Button_Archive().
; Создание самораспаковывающегося архива.
BreakCompress = #False
If CountFiles = 0
SetGadgetText(#Text_CurrentFiles, "Подсчет файлов")
WriteData(2, ?SFX_File, ?SFX_File_End-?SFX_File) ; В начало файла - модуль распаковки архива.
Count = 0
SetGadgetState(#ProgressBar_AllProgress, Count) ; Увеличение значения прогресса упаковки.
Break
EndIf
If Status <> #Z_OK And Status <> -1
Break
EndIf
Error = #False
SetGadgetText(#Text_CurrentFiles, "SFX архив создан")
Else
MessageRequester("", "Ошибка доступа к файлу при добавлении архива!", #MB_OK|#MB_ICONERROR)
MessageRequester("", "Не удалось создать файл!", #MB_OK|#MB_ICONERROR)
If FileSize(File) >= 0 ; и файл SFX архива был создан
DeleteFile(File) ; удаляем файл.
EndIf
SetGadgetState(#ProgressBar_FileProgress, 0)
В первую очередь, очищается динамически связанный список, имеющий имя Files(). Он был объявлен в начале программы. Затем вызывается процедура Find_File(), основное назначение которой – занести в связанный список имена и пути всех файлов, подлежащих архивации.
; Занесение путей к упаковываемым файлам в связанный список.
Procedure Find_File(Path.s, Sub.s)
If Directory
While NextDirectoryEntry(Directory) ; Следующий файл / папка.
ElseIf Type = #PB_DirectoryEntry_Directory ; Найдена папка
If Name <> "." And Name <> ".." ; Не текущая и не родительская папка.
; Рекурсивный вызов процедуры.
Find_File(Path + Name, Sub + Name + "\")
EndIf
EndIf
Wend
FinishDirectory(Directory) ; Завершение сканирования папки.
EndIf
Процедура
ищет файлы в выбранной
папке и во
всех подпапках. После того, как собранны данные об архивируемых файлах,
работа
процедуры завершается и управление передается процедуре Button_Archive().
Далее подсчитывается число элементов связанного
списка для того, чтобы узнать сколько файлов предстоит архивировать. С
помощью
функции CreateFile()
создается пустой файл –
заготовка для самораспаковывающегося архива. При помощи функции WriteData(),
в файл записывается модуль распаковки
архива. Напомню, этот модулю был добавлен в программу оператором IncludeBinary.
После чего, файл закрывается функцией CloseFile().
А вот дальше собственно и производится добавление файлов в
самораспаковывающийся архив. Все начинается с установки текущего пароля
архива,
который должен находиться в поле "Пароль архива". Если это поле
пустое, то пароль не устанавливается. При помощи функции PureZIP_Archive_Create(),
открывается ранее
созданный файл с уже записанным в него модулем распаковки. Флаг #APPEND_STATUS_CREATEAFTER
означает, что архив будет создан в конце файла. После этого, функцией SetCurrentDirectory()
изменяется текущая папка
программы. Ею становится папка с файлами, подлежащими добавлению в
архив. Это
необходимо для того, чтобы в архиве были сохранены относительные пути к
файлам,
а не абсолютные. Далее, в цикле ForEach
– Next,
производится перечисление всех
элементов списка Files() и
добавление в архив, файлов, пути к которым хранятся в
списке. Файлы добавляются в архив, функцией PureZIP_Archive_Compress().
Когда все файлы будут добавлены, функция PureZIP_Archive_Close()
закрывает архив. Все, самораспаковывающийся архив готов!
И
его можно использовать по назначению.
Если щелкнуть правой кнопкой мышки по файлу самораспаковывающегося архива, то можно заметить что в появившемся контекстном меню, есть пункт архиватора WinRAR (если конечно он установлен в системе) и есть возможность выполнять стандартные действия над архивом, то есть, распаковать или открыть его, добавить или удалить файлы папки и т. д. В свойствах файла, так же присутствует вкладка "Архив". Все это указывает на то, что данный самораспаковывающийся архив, по своему формату, совместим с аналогичным, создаваемым программой WinRAR.