Статьи                Главная

Создаем самораспаковывающийся 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.
Procedure ExtractArchive(ExtractPath.s)
 GlobalInfo.unz_global_info ; Объявление структур.
 
FileInfo
.PureZIP_FileInfo

  BreakExtract = #False      ; Создание локальных переменных.
 
DoneFiles   
= 2     ; Получение данных об первом (индекс 0) файле архива.

 
If PureZIP_GetFileInfo(ProgName, 0, @FileInfo) = #True
 
    If FileInfo\flag & 1 = 1 ; Установленный 0 бит - признак наличия пароля.
      Pass.s=InputRequester(GetFilePart(ProgName)+" - Архив защищён паролем!", "Введите пароль архива","")
      PureZIP_SetArchivePassword(Pass) ; Устанавливаем пароль, введенный пользователем.
    EndIf
 
EndIf

  If PureZIP_Archive_Read(ProgName) ; Открываем архив (исполняемый файл распаковщика).

    PureZIP_Archive_GlobalInfo(@GlobalInfo)
    NrOfCompressed.l = GlobalInfo\number_entry   ; Число файлов в архиве.
   
ReturnValue.l = PureZIP_Archive_FindFirst()  ;
Поиск первого файла в архиве.

    SetGadgetAttribute(#ProgressBar_All, #PB_ProgressBar_Maximum, NrOfCompressed)

    ; Цикл (While - Wend) распаковки всех файлов, содержащихся в архиве.
    While ReturnValue = #UNZ_OK
     
If
PureZIP_Archive_FileInfo(@FileInfo) = #UNZ_OK ; Данные о файле (размер, степень сжатия и др.).
        Status = PureZIP_Archive_Extract(ExtractPath, #True, @BreakExtract) ;
Распаковка файла на диск.

       
If BreakExtract = #True
          MessageRequester("", "Распаковка прервана пользователем", #MB_OK|#MB_ICONINFORMATION)
          Break
       
EndIf

        If Status = #UNZ_OK ; Успешная распаковка очередного файла.
          DoneFiles + 1
          SetGadgetText(#Text_ExtractFiles, Cut_FileName(ExtractPath+FileInfo\FileName))
          SetGadgetState(#ProgressBar_All, DoneFiles)
          ReturnValue = PureZIP_Archive_FindNext() ;
Поиск следующего файла для распаковки.
        Else
       
MessageRequester(
"","Произошла ошибка при распаковке"+Chr(10)+FileInfo\FileName,#MB_ICONWARNING)
          Break
       
EndIf

      EndIf
    Wend

   
PureZIP_Archive_Close() ; Закрытие архива.

   
If ReturnValue = #UNZ_END_OF_LIST_OF_FILE
      SetGadgetText(#Text_ExtractFiles, "Архив успешно распакован")
      MessageRequester(GetFilePart(ProgName), "Архив успешно распакован.", #MB_OK|#MB_ICONINFORMATION)
    Else ; Если не все файлы были распакованы (ошибка или операция прервана пользователем).
     
SetGadgetState(#ProgressBar_File, 0)
     
SetGadgetState(
#ProgressBar_All,  0)
     
SetGadgetText(
#Text_ExtractFiles, "")
   
EndIf

  
Else ; Обычно свидетельствует об повреждении архива.
   
MessageRequester(
"", "Ошибка при доступе к архиву.", #MB_OK|#MB_ICONERROR)
  EndIf

EndProcedure

Прежде всего, объявляются структуры и переменные, необходимые для работы с архивом. Далее, выясняется, защищен ли архив паролем. Для этого запрашивается информация об одном из файлов, который имеет нулевой индекс в архиве и анализируется состояние флагов и если нулевой бит установлен, то это признак того, что архив защищен паролем. В этом случае, при помощи функции InputRequester() будет создано диалоговое окно ввода данных, информирующее пользователя что для распаковки нужен пароль. 

Окно ввода пароля

Введенный пользователем пароль будет передан функции PureZIP_SetArchivePassword(), которая установит текущий пароль архива. После всего этого, можно открыть архив для чтения, что производится при помощи функции PureZIP_Archive_Read(), единственный аргумент которой – абсолютный путь к архиву, то есть к EXE-файлу самораспаковывающегося архива. Он заносится в строковую переменную ProgName при старте программы функцией ProgramFilename(). После успешного получения доступа к архиву, его содержимое можно извлечь в папку, заданную в строковой переменной ExtractPath. Данная операция производится в цикле WhileWend, который прервется при завершении распаковки, возникновении ошибки, либо при прерывании распаковки пользователем. При помощи функции PureZIP_Archive_FileInfo() запрашивается информация о текущем файле. Это необходимо для отображения имен файлов извлекаемых из архива. Далее при помощи функции PureZIP_Archive_Extract() производится извлечение текущего файла. Нужно отметить, что работа этой функции не завершится до тех пор, пока не будет распакован весь файл. Это особенно актуально для больших файлов, распаковка которых может занять продолжительное время. В первом аргументе функции указано место (папка), куда будет извлечен файл. От второго аргумента (#True или #False) зависит, будет ли при распаковке учитываться полный путь к файлу, сохраненный в архиве, или нет. Третий аргумент не обязателен и может отсутствовать. При его наличии, его значение будет передано в Calback функцию, в которой отображается прогресс извлечения файла из архива на диск. В рассматриваемой программе, передается указатель на переменную BreakExtract, в которой изначально хранится число 0 (#False). Если же пользователь прервет распаковку файлов, то в Callback функции, в переменную запишется число 1 (#True).

 Код Callback процедуры показан ниже. 

Код:

; Отображение прогресса распаковки обрабатываемого файла.
Procedure
File_Progress(File.s, Percent.f, *UserParam)

 SetGadgetState(#ProgressBar_File, PerCent) ; Отображение в окне прогресса распаковки.
  
Repeat

   
Event=WindowEvent()

    ;
Текущее событие программы.
   
If
Event=#PB_Event_CloseWindow ; Щелчок по крестику в заголовке окна.
      If EventWindow() = #Window_0
        Flags = #MB_YESNOCANCEL|#MB_ICONQUESTION|#MB_DEFBUTTON2
        Select MessageRequester("", "Закрыть окно с остановкой распаковки файлов?", Flags)
         
Case
#IDYES
           
End

         
Case
#IDCANCEL
           
PokeL
(*UserParam, #True)
            Break
        EndSelect
      EndIf
   
EndIf

  Until Event = 0

 
ProcedureReturn 0
EndProcedure

Она зарегистрирована в библиотеке PureZIP при помощи PureZIP_SetCompressionCallback() как функция отображения прогресса упаковки / распаковки файлов. Процедуре передается имя распаковываемого файла, процент его распаковки (в диапазоне 0 … 100) и данные, переданные в необязательном аргументе функции распаковки файла. В нашем случае, это указатель на локальную переменную BreakExtract процедуры ExtractArchive(). Хоть основное назначение процедуры File_Progress() состоит в отображении распаковки файла, но тот факт что при излечении файлов, она вызывается довольно часто, как минимум несколько раз в секунду, позволило возложить на нее еще и обработку событий программы на время распаковки файлов. Иначе бы пришлось распаковывать файлы в отдельном потоке. После того как при помощи функции SetGadgetState() был отображен текущий прогресс распаковки, выполняется код обработки событий программы. В цикле вызывается функция WindowEvent() до тех пор, пока не будут обработана вся очередь событий. Из всех событий, обрабатывается только одно – закрытие окна программы, при возникновении которого, на экране появится окно. 

Окно

При нажатии на кнопку "Да", работа программы завершится и ее окно закроется. В случае нажатия на кнопку "Нет", распаковка файлов продолжится. А если нажать на кнопку "Отмена", то распаковка файлов прекратится, но программа продолжит свою работу.

После компиляции программы получится модуль распаковки архива, размером 90 КБ. Но архива в нем естественно нет. Его еще предстоит добавить. А как его добавить? Можно конечно заранее создать ZIP архив со всеми требуемыми файлами, а потом дописать его в конец исполняемого файла, любым доступным способом и после архива, указать число добавленных байтов. Но этот метод усложняет создание самораспаковывающегося архива, поэтому создадим небольшую программу, автоматизирующую данный процесс. Ее окно показано на скриншоте. 

Окно

Программа позволяет создать самораспаковывающийся архив, защищенный паролем из файлов выбранной папки. Исполняемый файл модуля распаковки архива, хранится в секции данных программы. 

Код:

; Добавление при компиляции модуля распаковки в EXE файл упаковщика.
DataSection
  SFX_File:
  IncludeBinary "SFX.exe"
 
SFX_File_End:
EndDataSection

Он туда помещается при помощи оператора IncludeBinary на этапе компиляции программы. Метки до и после этого оператора, позволяют получить данные, загруженные оператором IncludeBinary. Такой способ хранения модуля распаковки хорош тем, что программа состоит всего из одного исполняемого файла, а один файл хранится внутри другого.

Самораспаковывающийся архив создает процедура Button_Archive()

Код:

; Создание самораспаковывающегося архива.

Procedure Button_Archive(Directory.s, File.s)

  Error = #True
 
BreakCompress = #False

  SetGadgetText(#Text_CurrentFiles, "Подсчет файлов. Ждите...")
  ClearList( Files() )     ; Очистка динамически связанного списка.
  Find_File(Directory, "") ; Поиск файлов, подлежащих архивации.

  CountFiles = ListSize( Files() ) ; Число найденных файлов.
 
If
CountFiles = 0
 
   MessageRequester("", "В выбранной папке отсутствуют файлы!", #MB_OK|#MB_ICONWARNING)
    ProcedureReturn
  EndIf

  SetGadgetAttribute(#ProgressBar_AllProgress, #PB_ProgressBar_Maximum, CountFiles)
 
SetGadgetText
(#Text_CurrentFiles, "Подсчет файлов")
  CurrentDir.s = GetCurrentDirectory() ; Путь к текущей папке программы.

  If CreateFile(2, File) ; Создаем файл самораспаковывающегося архива.
   
WriteData
(2, ?SFX_File, ?SFX_File_End-?SFX_File) ; В начало файла - модуль распаковки архива.
    CloseFile(2)

    Password.s = GetGadgetText(#String_Password) ; Пароль архива.
    PureZIP_SetArchivePassword(Password)

    If PureZIP_Archive_Create(File, #APPEND_STATUS_CREATEAFTER) ; Добавление архива в конец файла.
      SetCurrentDirectory(Directory)                            ; Установка текущей папки.
     
Count = 0

     
ForEach Files() ; В цикле упаковываются файлы, имена которых находятся в связанном списке.

        Count + 1
       
SetGadgetState
(#ProgressBar_AllProgress, Count) ; Увеличение значения прогресса упаковки.
        SetGadgetText(#Text_CurrentFiles, Cut_FileName(Directory+Files()))
        Status = PureZIP_Archive_Compress(Files(), #True, @BreakCompress) ;
Упаковка файла.

       
If BreakCompress = #True
          MessageRequester("", "Создание архива прервано пользователем", #MB_OK|#MB_ICONINFORMATION)
         
Break

       
EndIf

        
If Status <> #Z_OK And Status <> -1
          MessageRequester("", "Возникла ошибка при упаковке файла"+Chr(10)+Files(),  #MB_OK|#MB_ICONERROR)
         
Break

       
EndIf

      Next

     
PureZIP_Archive_Close() ; Завершение создания архива.

     
If CountFiles = Count
       
Error = #False

       
SetGadgetText
(#Text_CurrentFiles, "SFX архив создан")
        MessageRequester("", "Самораспаковывающийся архив создан.", #MB_OK|#MB_ICONINFORMATION)
      EndIf

    
Else
     
MessageRequester
("", "Ошибка доступа к файлу при добавлении архива!", #MB_OK|#MB_ICONERROR)
    EndIf
  Else
   
MessageRequester
("", "Не удалось создать файл!", #MB_OK|#MB_ICONERROR)
  EndIf    SetCurrentDirectory(CurrentDir) ; Установка текущей папки программы.

 
If Error = #True         ; Если произошла ошибка,
    If FileSize(File) >= 0 ; и файл SFX архива был создан,
     
DeleteFile
(File)     ; удаляем файл.
   
EndIf
    SetGadgetText(#Text_CurrentFiles, "SFX архив не был создан")
   
SetGadgetState
(#ProgressBar_FileProgress, 0)
    SetGadgetState(#ProgressBar_AllProgress,  0)
  EndIf

EndProcedure

В первую очередь, очищается динамически связанный список, имеющий имя Files(). Он был объявлен в начале программы. Затем вызывается процедура Find_File(), основное назначение которой – занести в связанный список имена и пути всех файлов, подлежащих архивации.

Код:

; Занесение путей к упаковываемым файлам в связанный список.
Procedure Find_File(Path.s, Sub.s)

  If Right(Path.s,1)<>"\":Path + "\":EndIf    ; Начало сканирования папки.
  Directory = ExamineDirectory(#PB_Any, Path, "*.*")
 
If
Directory
   
While
NextDirectoryEntry(Directory) ; Следующий файл / папка.

      Type.l = DirectoryEntryType(Directory) ; Тип объекта (файл или папка).
      Name.s = DirectoryEntryName(Directory) ; Имя объекта.

     
If Type = #PB_DirectoryEntry_File ; Найден файл.
        AddElement( Files() )           ; Добавления элемента в список.
        Files() = Sub + Name            ; Запись в список имени файла.
     
ElseIf Type = #PB_DirectoryEntry_Directory ; Найдена папка.
       
If
Name <> "." And Name <> ".." ; Не текущая и не родительская папка.
          
; Рекурсивный вызов процедуры.
         
Find_File
(Path + Name, Sub + Name + "\")
        EndIf
     
EndIf

    
Wend
   
FinishDirectory
(Directory) ; Завершение сканирования папки.
 
EndIf


EndProcedure

Процедура ищет файлы в выбранной папке и во всех подпапках. После того, как собранны данные об архивируемых файлах, работа процедуры завершается и управление передается процедуре Button_Archive(). Далее подсчитывается число элементов связанного списка для того, чтобы узнать сколько файлов предстоит архивировать. С помощью функции CreateFile() создается пустой файл – заготовка для самораспаковывающегося архива. При помощи функции WriteData(), в файл записывается модуль распаковки архива. Напомню, этот модулю был добавлен в программу оператором IncludeBinary. После чего, файл закрывается функцией CloseFile(). А вот дальше собственно и производится добавление файлов в самораспаковывающийся архив. Все начинается с установки текущего пароля архива, который должен находиться в поле "Пароль архива". Если это поле пустое, то пароль не устанавливается. При помощи функции PureZIP_Archive_Create(), открывается ранее созданный файл с уже записанным в него модулем распаковки. Флаг #APPEND_STATUS_CREATEAFTER означает, что архив будет создан в конце файла. После этого, функцией SetCurrentDirectory() изменяется текущая папка программы. Ею становится папка с файлами, подлежащими добавлению в архив. Это необходимо для того, чтобы в архиве были сохранены относительные пути к файлам, а не абсолютные. Далее, в цикле ForEachNext, производится перечисление всех элементов списка Files() и добавление в архив, файлов, пути к которым хранятся в списке. Файлы добавляются в архив, функцией PureZIP_Archive_Compress(). Когда все файлы будут добавлены, функция PureZIP_Archive_Close() закрывает архив. Все, самораспаковывающийся архив готов!  И его можно использовать по назначению.

Если щелкнуть правой кнопкой мышки по файлу самораспаковывающегося архива, то можно заметить что в появившемся контекстном меню, есть пункт архиватора WinRAR (если конечно он установлен в системе) и есть возможность выполнять стандартные действия над архивом, то есть, распаковать или открыть его, добавить или удалить файлы папки и т. д. В свойствах файла, так же присутствует вкладка "Архив". Все это указывает на то, что данный самораспаковывающийся архив, по своему формату, совместим с аналогичным, создаваемым программой WinRAR.

Скриншот WinRAR


Скачать файлы прилагаемые к статье.



Статьи                Главная

Сайт создан в системе uCoz