書庫ファイルの展開用スクリプト #PPx

[#PPx 書庫ファイルの展開用スクリプト](http://d.hatena.ne.jp/hokorobi/20130602/1370178476) の焼き直し。
オブジェクト指向っぽくしてみた。
マシなコードになっているはず。
テストはほとんどしていない。

```js:unpack_7z.js
//!*script
// 第一引数: レスポンスファイル
// 第二引数: 展開先フォルダ
//
// 7z を実行して展開先フォルダにアーカイブファイルを展開する。
// 書庫をそのまま展開したときに 1 フォルダにまとまるときは、書庫名フォルダを作成しない。
// 1 フォルダにまとまっていないときは書庫名フォルダ作成して、その中に展開する。
// 同じ名前のフォルダが存在する場合は、-1 などを末尾に追加したフォルダ名で展開する。
// 展開中に別のファイルを展開しようとした場合、キューイングする。
// レスポンスファイルの中身は、アーカイブファイル名をフルパス、"" 括りなしで列挙する。
//
// PPx 1.43+2 x64 + Script Module R14

if (PPx.Arguments.Length < 2) {
	linemessage('引数が正しくありません。', false);
	PPx.Quit();
}

var UnpaQ = new Queue('unpackQueue');
UnpaQ.enqueue([PPx.Arguments.Item(0), PPx.Arguments.Item(1)]);

var Unpacking = new Cust('unpacking');

// 展開処理中のプロセスが既にある場合、当該プロセスは停止する。
if (Unpacking.get() != '') PPx.Quit();
Unpacking.set('prep');

for (var unpackset=UnpaQ.dequeue(); unpackset.length > 0; unpackset=UnpaQ.dequeue()) {
	var archives = new Archives(unpackset);
	archives.extract();
	delete archives;
}

Unpacking.clear();

function Archives(unpackset) {
	this.response = unpackset[0];
	this.folder = unpackset[1];

	this._getArchiveFiles = function() {
		try {
			var f = new ActiveXObject('ADODB.Stream');
			f.Type = 2;  // 1:バイナリデータ 2:テキストデータ
			f.Charset = 'UTF-8';  // 入力ファイルの文字コード設定
			f.Open;
			f.LoadFromFile(this.response);  // 入力ファイルを読み込む
			var archives = f.ReadText(-1).split(/\r\n/);
			f.Close();
		} catch (e) {
			linemessage('アーカイブファイルリストを取得できませんでした。: ' + this.response);
			return [];
		}
		return archives;
	}

	this.extract = function() {
		var archives = this._getArchiveFiles();
		for (var i = 0; i < archives.length; i++) {
			var archive = new Archive(archives[i], this.folder);
			try {
				archive.extract();
			} catch (e) {
				linemessage(e.message);
				linemessage('展開失敗: ' + archive.file);
			}
			// delete archive;
		}
	}
}

function Queue(name) {
	this.name = name;
	this.cust = new Cust(this.name);

	this.enqueue = function(array) {
		var value = array.join('@'); // 順序の保証はあるのかな？
		var values = this.cust.get();
		values += (values == '') ? value : ';' + value;
		this.cust.set(values);
	};

	this.dequeue = function() {
		var values = this.cust.get();
		if (values == '') return [];

		var arrays = values.split(';');
		if (arrays.length == 0) return [];

		value = arrays[0];
		var array = value.split('@');

		arrays.splice(0, 1);
		if (arrays.length > 0) this.cust.set(arrays.join(';'));
		else this.cust.clear();

		return array;
	};

	this.clear = function() {
		this.cust.clear();
		delete this.cust;
	}
}

function Cust(name) {
	this.name = name;

	this.set = function(value) {
		PPx.Execute('*setcust _User:' + this.name + '=' + value);
	};
	this.clear = function() {
		this.set('');  // -| を使ったら _User がすべて消えた
	};
	this.get = function() {
		return PPx.Extract('%*getcust(_User:' + this.name + ')');
	};
}

function Archive(file, folder) {
	this.file = file;
	this.folders = {target:folder, from:'', to:'', del:''};
	this.fso = new ActiveXObject('Scripting.FileSystemObject');

	this._getFolders = function() {
		var tempfolder = this.folders.from;
		var oTempFolder = this.fso.GetFolder(tempfolder);
		var oSubFolders = oTempFolder.SubFolders;

		// 展開したフォルダ直下がフォルダ一つでないなら直接移動
		if (oTempFolder.files.Count > 0 || oSubFolders.Count > 1) return;

		// 展開したフォルダ直下がフォルダ一つなら、そのフォルダを移動
		var e = new Enumerator(oSubFolders);  // サブフォルダを取得するために Enumerator を使う
		var oSubfolder = this.fso.GetFolder(e.item());
		this.folders['from'] = this.fso.BuildPath(tempfolder, oSubfolder.Name);
		this.folders['to'] = this.fso.BuildPath(this.folders.target, oSubfolder.Name);
		// 同一フォルダが存在した場合に -1 などを付加する
		this.folders['to'] = PPx.Extract('%*name(DCNU, "' + this.folders.to + '")');
		this.folders['del'] = tempfolder;
		return;
	};

	this._moveFormalFolder = function() {
		// FIXME: 書き込めません。のエラーになることがある
		// まだ発生する？
		this.fso.MoveFolder(this.folders.from, this.folders.to);
		if (this.folders['del'] == '') return;

		var oFolder = this.fso.GetFolder(this.folders.del);
		oFolder.Delete(true);
	};

	this.extract = function() {
		if (!this.fso.FolderExists(this.folders.target)) throw '展開先のフォルダが存在しません。: ' + this.folders.target;

		do {
			var tempFolder = this.fso.BuildPath(this.folders.target, this.fso.GetTempName());
		} while (this.fso.FolderExists(tempFolder));

		Unpacking.set(this.file);
		var cmd;
		// 処理が完了していないとフォルダの移動ができないので %Os を使う。
		// PPb で実行すると複数実行されてしまうので %Ob を使う。
		cmd = "%Onbsq %'7z' x \"" + this.file;
		cmd += (this.file.match(/\.tar\.gz$|\.tgz$|\.tbz$|\.tar.bz2$|\.tar.xz$/)) ? "\" -bd -so | %'7z' x -ttar -si -o\"" : '" -bd -o"';
		cmd += tempFolder + '"';
		PPx.Execute(cmd);  // エラーを拾えない

		// 展開できていなかったら失敗とする
		if (!this.fso.FolderExists(tempFolder)) {
			linemessage('展開先のフォルダが見つかりません: ' + tempFolder);
			linemessage('展開失敗: ' + this.file);
			return;
		}

		var formalFolder = this.fso.BuildPath(this.folders.target, this.fso.GetBaseName(this.file));
		this.folders['from'] = tempFolder;
		this.folders['to'] = formalFolder;
		this._getFolders();
		this._moveFormalFolder();
	};
}

function linemessage(message) {
	PPx.Execute('*execute C,*logwindow "unpack_7z: ' + message + '"');
}
```