1 (изменено: Rumata, 2012-04-19 05:11:24)

Тема: JScript: Работа с ZIP-архивами: расширенная версия

Как уже сказано в сообщении #5 темы JScript: работа с ZIP архивами предыдущая работа доступна но только в архиве "Разработки". В настоящей теме предлагаю рассмотреть, обсудить и покритиковать новую версию класса для работы с ZIP-архивами.

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

Вначале будет дано краткое описание API, показаны примеры использования, затем приведен исходный код и ссылка на актуальную версию библиотеки. Критика, замечания и багрепорты приветствуются.

***

API библиотеки

Конструктор
ZipFile(name, create, overwrite)
Конструктор, создает новый объект для работы с архивами. Фактически вызывает метод ZipFile#open, который выполняет все необходимые действия

Свойства
ZipFile#name
Полный путь до архивного файла

ZipFile#file
Объект Folder, виртуальная папка проводника, реализующая доступ к архиву

Методы
ZipFile#open(name, create, overwrite)
Открывает архивный файл. Если аргумент create == true, то создается новый файл. Прежнее значение будет перезаписано, если указан аргумент overwrite == true. Инициализирует два свойства:

ZipFile#flush(timeout)
Сброс данных в архив. Метод-подделка необходима для создания задержки в timeout миллисекунд, чтобы предотвратить завершение скрипта раньше завершения создания архива.

ZipFile#forInput(input, func)
Метод используется для помещения объектов из массива input в архив. Для этого вторым аргументом передается функция, которая будет вызвана для каждого элемента массива. Функция использует два аргумента
1. объект File или Folder, который будет помещен в архив
2. объект Folder, виртуальная папка проводника, реализующая доступ к архиву

ZipFile#forEach(func)
Метод используется для работы с объектами внутри архива. Для работы с объектами методу передается функция, которая вызывается для каждого объекта в архиве. Функция использует два аргумента
1. объект File или Folder в архиве
2. объект Folder, виртуальная папка проводника, реализующая доступ к архиву

***

Пример 1. Создание ZIP-архива


var filename = 'test.zip';
var create = true;
var overwrite = true;

WScript.Echo('*** Create the file');
var zipfile = new ZipFile();
zipfile.open(filename, create, overwrite);

WScript.Echo('*** Put objects to the archive');
zipfile.forInput(
	[
		'C:\\Logs', 
		'C:\\tmp', 
	], 
	function(folderItem, arcfile)
	{
		WScript.Echo(folderItem.Path);

		arcfile.CopyHere(folderItem.Path, 0);
		WScript.Sleep(3000);
	}
);

В этом примере создается архивный файл test.zip, в который будут помещены существующие каталоги C:\Log и C:\tmp. Существовавший до этого файл test.zip будет перезаписан.
Было замечено, что при увеличении размера архивного файла или архивировании большого количества файлов требуется некоторое время перед помещением следующего элемента. Поэтому используется задержка в 3 секунды после копирования файлов в архив.
Для копирования объектов в архив вызывается функция, которой передаются два аргумента:
1. объект File или Folder, который будет помещен в архив
2. объект Folder, виртуальная папка проводника, реализующая доступ к архиву


Пример 2. Чтение списка файлов из архива


var filename = 'test.zip';

WScript.Echo('*** Open the file');
var zipfile = new ZipFile(filename);

WScript.Echo('*** Show the content of the archive recursively');
zipfile.forEach(function(folderItem, arcfile)
{
	if ( ! folderItem.IsFolder ) {
		WScript.Echo(folderItem.Path, folderItem.Size);
		return;
	}

	WScript.Echo(folderItem.Path);

	var items = folderItem.GetFolder.Items();
	var len = items.Count;
	for (var i = 0; i < len; i++) {
		var g = items.Item(i);
		arguments.callee(g, arcfile);
	}
});

В этом примере открывается архивный файл test.zip и выводится рекурсивно список всех файлов и каталогов, содержащихся в архиве. Дополнительно, для каждого файла в архиве выводится его реальный размер. Метод вызывает заданную функцию для каждого объекта в архиве. В функцию передаются два аргумента:
1. объект File или Folder, находящийся в архиве
2. объект Folder, виртуальная папка проводника, реализующая доступ к архиву

***

Исходный код библиотеки
Здесь помещена последняя редакция библиотеки (удалена шапка с примерами, которые уже были рассмотрены чуть ранее). Актуальная версия доступна по адресу http://code.google.com/p/jsxt/source/br … ZipFile.js



/**
 * Creates new object to work with archives. It can accept the same 
 * arguments as the ZipFile#open method. For details see the description 
 * of the method. 
 *
 * @return	ZipFile
 * @acess	public
 * @see		ZipFile#open
 */
function ZipFile()
{
	if ( arguments.length == 0 ) {
		return;
	}
	this.open.apply(this, arguments);
};

(function()
{

var fso = new ActiveXObject('Scripting.FileSystemObject');
var shellApp = new ActiveXObject("Shell.Application");

/**
 * Opens a specified file. Creates a file if it was pointed. The name is 
 * mandatory argument passing the name of the existing archive or the name 
 * of the file that will be created. In the last case additional arguments 
 * are needed. 
 *
 * @param	String	Mandatory. String expression that identifies the 
 * 			existing file or the file to create. 
 * @param	Boolean	Optional. Boolean value that indicates whether a 
 * 			new file can be created 
 * @param	Boolean	Optional. Boolean value that indicates whether you 
 * 			can overwrite an existing file. It depends on the 
 * 			create argument is true. 
 *
 * @return	void
 * @acess	public
 */
ZipFile.prototype.open = function(name, create, overwrite)
{
	if ( create ) {
		var zipFile = fso.CreateTextFile(name, !! overwrite, 0);
		zipFile.Write('PK\05\06' + new Array(19).join('\0')); // 18 zeros
		zipFile.Close();
	}

	this.name = fso.GetFile(name).Path;
	this.file = shellApp.NameSpace(this.name);
};

/**
 * Fake method. It allows a script to flush data to a disk to complete 
 * processing. In fact, it just suspends script execution for a specified 
 * length of time. 
 *
 * @param	Number	Integer value indicating the interval (in 
 * 			milliseconds) you want the script process to be 
 * 			inactive. 
 *
 * @return	void
 * @access	public
 */
ZipFile.prototype.flush = function(timeout)
{
	timeout = Math.max(Number(timeout) || 0, 100);
	WScript.Sleep(timeout);
};

/**
 * Executes a provided function once for each item from within a archive. 
 * Once called it allows to perform any action with items from an archive.
 * The callback function is invoked with two arguments:
 * -- The FolderItem argument refers to the real object (file or folder) 
 *    within the archive 
 * -- The Folder object that refers to the archive file
 *
 * @param	Function	Mandatory. A function that will be called 
 * 				for each item in the archive
 *
 * @return	void
 * @access	piblic
 */
ZipFile.prototype.forEach = function(func)
{
	if ( typeof func != 'function' ) {
		throw new TypeError();
	}

	var arcfile = this.file;
	var items = arcfile.Items();

	for (var e = new Enumerator(items); ! e.atEnd(); e.moveNext()) {
		var folderItem = e.item();
		func(folderItem, arcfile);
	}
};

/**
 * Executes a provided function once for each item before input it to the 
 * archive. Once called it allows to perform any action with items. The 
 * callback function is invoked with two arguments: 
 * -- The FolderItem argument refers to the real object (file or folder) 
 *    before putting it into the archive 
 * -- The Folder object that refers to the archive file
 *
 * @param	Array/String	Mandatory. The list of items taht will be 
 * 				put into the archive. 
 * @param	Function	Mandatory. A function that will be called 
 * 				before an item will be put into the 
 * 				archive 
 *
 * @return	void
 * @access	piblic
 */
ZipFile.prototype.forInput = function(input, func)
{
	if ( typeof func != 'function' ) {
		throw new TypeError();
	}

	var arcfile = this.file;

	if ( typeof input == 'string' ) {
		input = [input];
	}

	for (var i = 0; i < input.length; i++) {
		var p = input[i];
		if ( ! p ) {
			continue;
		}

		var folderItem = null;
		if ( fso.FileExists(p) ) {
			folderItem = fso.GetFile(p);
		} else if ( fso.FolderExists(p) ) {
			folderItem = fso.GetFolder(p);
		}

		func(folderItem, arcfile);
	}
};

})();
( 2 * b ) || ! ( 2 * b )