Total Commander Forum Index Total Commander
Форум поддержки пользователей Total Commander
Сайты: Все о Total Commander | Totalcmd.net | Ghisler.com | RU.TCKB
 
 RulesRules   SearchSearch   FAQFAQ   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Single Post  Topic: Уникальность HANDLE в архиваторном плагине 
Author Message
Mowgli



PostPosted: Fri Jun 26, 2009 23:15    Post subject: Reply with quote

VadiMGP wrote:
Наверно можно создать такую ситуацию. В комбинации с WDX плагинами.

Worros wrote:
По-моему невозможна. ...это нарушение стандарта ТС.

VadiMGP wrote:
Лет пять назад он даже согласился и пообещал добавить этот бит.
Вот со дня на день ждем-с... Smile

Какую животрепещущую тему я затронул =) Вот что значит взгляд со стороны.
Ну в любом случае я понял, что иду верной дорогой.
Quote:
Но почему тебя волнует именно распаковка?

Вероятно просто потому, что я сейчас делаю плагин для распаковки. Да и что изменится в плане вышеобсуждённого вопроса для других операций?

Меня уже другое интересует.
Вот например описание функций OpenArchive, CloseArchive, ReadHeader и ProcessFile весьма неоднозначное и мутное. Кроме того:
- во-первых, никак нельзя описывать работу этих функций по-отдельности, поскольку они являются частью одного алгоритма.
- во-вторых, надо их описывать по-возможности в привязке к происходящему со стороны пользовательского интерфейса.

Я сейчас напишу своё понимание этого вопроса (только для случая распаковки), и прошу подтвердить или поправить.

Главный факт: открытие архива и распаковка из него - это разные и независимые циклы работы плагина. Из документации это можно понять только косвенно.
Вкратце, цикл это:
- в начале разовый вызов OpenArchive
- вызов ReadHeader и ProcessFile последовательно для каждого файла в архиве (подробности опишу далее)
- в конце разовый вызов CloseArchive

Теперь подробнее.
I. Когда пользователь заходит в архив, то первым делом TC должен получить список файлов. Для этого:
1. Вызывается функция
HANDLE __stdcall OpenArchive(tOpenArchiveData *ArchiveData);
параметр ArchiveData->ArcName содержит имя файла архива
ArchiveData->OpenMode установлен в PK_OM_LIST
Функция должна подготовить данные для последующего получения списка файлов и каталогов в архиве. Стратегии могут быть разные. Можно открыть архив, сразу составить этот список и закрыть архив. Можно открыть файл архива и настроить всё для последующего последовательного чтения. В любом случае, необходимо как-то запомнить всё сделанное (наиболее подходящим местом выглядит динамическая память) и вернуть в качестве результата вызова число типа HANDLE. Это может быть например указатель на созданную структуру или дескриптор открытого файла. Лично я практикую объектно-ориентированный подход, так что с архивом у меня связан объект соответствующего класса, созданный в хипе, а возвращаю я указатель на этот объект.
2. Далее TC получает от плагина список файлов в архиве. Для этого он начинает вызывать функцию
int __stdcall ReadHeader(HANDLE hArcData, tHeaderData *HeaderData);
С каждым вызовом (кроме последнего, см. далее) плагин возвращает информацию строго об одном файле.
здесь hArcData - это тот указатель, который был ранее получен фунцией OpenArchive
HeaderData - это структура, которую надо заполнить информацией о текущем файле из архива. Эту информацию получит TC. Все незаполненные поля надо обнулить.
Если ReadHeader возвращает информацию о файле (т.е. заполняет HeaderData), то сама функция должна вернуть 0.
Если файлов больше нет, то заполнять HeaderData не надо, а функция должна вернуть E_END_ARCHIVE. После этого функция больше не вызывается.
Внимание! Это всё означает, что функция ReadHeader будет вызвана число раз на 1 больше, чем есть в архиве файлов и каталогов.

При организации перечисления возникает естественная задача как-то хранить последний найденный файл (или каталог). Эту информацию (итератор в списке, позицию чтения в файле и т.п.) надо хранить в структурах данных, связанных с аргументом функции hArcData. Напоминаю, что у меня это указатель на мой объект. Список файлов я составляю уже в функции OpenArchive, там же завожу итератор. В функции ReadHeader я просто читаю данные по этому итератору и смещаю его на следующий элемент.

Для каждого найденного файла, т.е. после каждого вызова ReadHeader, который вернул 0, происходит вызов функции
int __stdcall ProcessFile(HANDLE hArcData, int Operation, char *DestPath, char *DestName);
где Operation имеет значение PK_SKIP. Это означает, что делать здесь ничего не надо. Можно однако предположить ситуацию, что для перечисления файлов надо их фактически распаковать. А поскольку распаковка позже будет при следующих циклах происходить именно в этой функции, то вызов этой функции и для простого открытия архива выглядит логичным. Ещё раз однако, чаще всего при перечислении файлов здесь не надо делать ничего.

В документации в разделе о струтуре tOpenArchiveData написано:
If the file is opened with OpenMode==PK_OM_LIST, ProcessFile will never be called by Total Commander.
Т.е. утверждается, что ProcessFile не будет вызываться, если файл был открыт для составления списка файлов. Это неправда и противоречит написанному в том-же документе в разделе о функции ProcessFile. И эксперимент подтверждает, что ProcessFile вызывается в любом случае.

Ещё раз. ProcessFile вызывается ровно столько раз, сколько было найдено файлов и каталогов в архиве (т.е. столько раз, сколько вызов ReadHeader вернул 0).

3. После того как ReadHeader возвращает [/b]E_END_ARCHIVE[/b] TC вызывает функцию
int __stdcall CloseArchive (HANDLE hArcData);
Всё, что требуется от этой функции - почистить за собой созданные данные. Напомню, что у меня там был объект, и всё, что мне нужное здесь сделать это вызвать операцию delete (я пишу на C++). В принципе и эта функция может вернуть ошибку, но учитывая, что деструкторы исключений не вызывают =), у меня одним геморроем меньше.

Первый цикл работы плагина закончился! На основе полученной информации TC строит дерево каталогов внутри архива. Дальнейшая навигация внутри архива и даже повторные заходы в этот архив осуществляются без участия плагина.

II. Теперь пользователь выбрал некоторые файлы и каталоги, установил путь назначения и нажал "распаковать". Теперь TC запускает второй самостоятельный цикл работы плагина:
1. Вызывается функция OpenArchive
ArchiveData->OpenMode установлен в PK_OM_EXTRACT
параметр ArchiveData->ArcName как и раньше содержит имя файла архива и его надо открывать повторно.
Общая идея этой функции сохраняется, но теперь надо заготовить всё для последующей распаковки. Это может быть тот же набор действий, что и для получения списка файлов, а может потребовать и больше работы (для этого вероятно циклы и сделаны независимыми)

2. Далее TC вызывает последовательно ReadHeader[[b]b] и [b]ProcessFile для каждого из файлов и каталогов, полученных от плагина при открытии файла, и строго в том порядке, в котором он их получал.
Эта часть похожа на происходившее ранее при перечислении файлов. Есть однако и отличия. Самое первое заключается в том, что вызовов как ReadHeader так и ProcessFile будет ровно столько, сколько есть файлов и каталогов (поскольку TC уже имеет информацию об их количестве).

Теперь по порядку.
ReadHeader
Вызывается совершенно так-же, как и до этого при перечислении файлов. Казалось бы, у TC уже есть имена файлов из архива, мог бы и передать нам, дабы не извлекать их ещё раз. Но нет. Фактически, всю работу по созданию списка надо повторить заново. В этом цикле возникает дополнительная задача. Надо передать информацию о текущем файле в архиве последующему вызову ProcessFile, поскольку ему TC передаёт только путь назначения. Очевидно, что эту информацию (строка, итератор и пр. в этом роде) надо сохранить в структурах данных, связанных с тем самым hArcData. У меня это просто дополнительный член моего класса - строка с именем текущего файла.

ProcessFile
В качестве аргумента Operation получает значение
- PK_TEST - для тестирования или PK_EXTRACT - для распаковки для всех отмеченных пользователем файлов. Подчёркиваю - только файлов, не каталогов.
- PK_SKIP - для всех не отмеченных файлов и для всех каталогов. С файлами всё ясно, а с каталогами ситуация следующая. TC сам создаёт все каталоги, которые отмечены для распаковки, при этом даёт им текущее время создания. В принципе, ничто не мешает при распаковке не игнорировать каталоги, а менять их атрибуты и время на те, что указаны в архиве. Но надо помнить про ситуацию распаковки в уже существующую каталожную структуру. Существовал ли каталог до распаковки или был сделан только что? Проще не заморачиваться на этот счёт.
Полный путь для распаковки содержится в двух аргументах DestName и DestPath. В офф. документации замечательно написано:
Either DestName contains the full path and file name and DestPath is NULL, or DestName contains only the file name and DestPath the file path.
Т.е. либо
DestName = "c:\dir\file.txt"
DestPath = NULL

либо
DestName = "file.txt"
DestPath = "c:\dir\"

Как это понимать? Надо проверять по какому варианту сейчас взбрело в голову TC передать этот путь? У меня пока всегда срабатывало по первому варианту (DestName содержит полный путь включая имя, а DestPath = NULL).
А вот какой файл в архиве распаковывать, придётся узнавать самостоятельно. TC это несомненно знает, но молчит. Расчёт очевидно идёт на то, что обход файлов осуществляется строго в том же порядке, что и при получении списка файлов. Соответственно для решения этой задачи мы и запомнили текущий файл в предшествующем вызове ReadHeader.

3. CloseArchive как и раньше завершает всю цепочку действий. Чистим за собой все данные.

Всё! Все последующие распаковки происходят по такому же алгоритму.

З.Ы.: Что-то в процессе я разошёлся малость, и вышло даже маленькое руководство. Жаль, что под спойлер не спрятать.
View user's profile Send private message


Powered by phpBB © 2001, 2005 phpBB Group