UNPKG

cejs

Version:

A JavaScript module framework that is simple to use.

1,134 lines (1,009 loc) 35.2 kB
/** * @name CeL functions for archive file. * @fileoverview 本檔案包含了壓縮封裝/抽取抽出 compress / extract archive file 的 functions。 * * 注意: 這需要先安裝 7z.exe 程式。 * * 注意: macOS 下似乎可用 / 與 \ 作目錄名稱? * * <code> // {Object|String}options.switches: additional command line switches to list archive_file = new CeL.application.storage.archive('file.zip', callback); archive_file = new CeL.application.storage.archive('file.7z', callback, options); archive_file = new CeL.application.storage.archive('file.rar', callback); // {String} archive_file.program = executable_file_path['7z']; archive_file.execute(switches, callback); // FSO status hash get from archive_file.info() archive_file.fso_path_hash = { FSO path : {FSO data}, ... } archive_file.information = { archive information } // list FSOs, get FSO status hash // callback({fso_status}); // fso_status = {path:'',name:'',size:0,modify:{Date},create:{Date}} archive_file.info(options, callback); archive_file.info(options, callback); // compress / create / add new FSOs to archive_file // {Object|String}options.switches: additional command line switches archive_file.update([ file/folder list to compress, add, update ], options, callback); archive_file.update([ file/folder list to compress, add, update ], { type : 'zip', // level of compression, compression method // '' as -mx level : '', }, callback); archive_file.remove([ to_delete ]); archive_file.remove([ to_delete ], options, callback); // {Array|String}options.list: FSO path list to extract // {Object|String}options.switches: additional command line switches archive_file.extract({output : target_directory}, callback); archive_file.extract([ files to extract ], {output : target_directory, other options}, callback); // 請注意: rename 必須先安裝 7-Zip **16.04 以上的版本**。 // {Array}pairs=[from,to,from,to] // TODO: {Object}pairs={from:to,from:to} archive_file.rename(pairs, callback); TODO: // test archive_file // {Object|String}options.switches: additional command line switches // {String}options.switches.password: password archive_file.verify(options, callback); // https://github.com/ObjSal/p7zip/blob/master/GUI/Lang/ja.txt </code> * * @see CeL.application.OS.Windows.archive * @see https://github.com/quentinrossetti/node-7z https://stuk.github.io/jszip/ * * @since 2018/3/4 13:57:28 * @since 2018/3/8 19:59:47 初步可用 */ 'use strict'; // 'use asm'; // -------------------------------------------------------------------------------------------- if (typeof CeL === 'function') { // 忽略沒有 Windows Component Object Model 的錯誤。 CeL.env.ignore_COM_error = true; CeL.run({ // module name name : 'application.storage.archive', // .includes() @ CeL.data.code.compatibility require : 'data.code.compatibility.' // run_JScript() @ CeL.application.platform.nodejs // executable_file_path() @ CeL.application.platform.nodejs + '|application.platform.nodejs.', // 設定不匯出的子函式。 no_extend : '*', // 為了方便格式化程式碼,因此將 module 函式主體另外抽出。 code : module_code }); } function module_code(library_namespace) { // requiring var path_separator = library_namespace.env.path_separator; var execSync = require('child_process').execSync; // -------------------------------------------------------------------------------------------- // 初始化、檢測可用壓縮工具。 // @see CeL.application.OS.Windows.execute // @see CeL.application.platform.nodejs.executable_file_path // search executable file path / 執行檔, `which` var executable_file_path = { // filename extension : executable file path // wrapper for 7-Zip // cache the path of p7z executable file // library_namespace.application.platform // .execute.search([ "7z", "p7z" ], "-h") '7z' : library_namespace.executable_file_path([ "7z", "p7z", /** * <code> e.g., install p7zip package via yum: # sudo yum install epel-release # sudo yum install p7zip p7zip-plugins In Debian, Ubuntu, Linux Mint: # sudo apt-get install p7zip-full p7zip-rar In macOS: install https://www.keka.io/ </code> */ "7za", // for Linux Mint 19.1 Cinnamon // 7zr is a stand-alone executable. 7zr is a "light-version" of 7za(1). // 7zr handles password-less archives in the 7z, LZMA2, and XZ formats // only. // 7zr 無法處理 zip file // "7zr", // keka @ macOS "/Applications/Keka.app/Contents/Resources/keka7z", library_namespace.platform('windows') // '"' + (process.env.ProgramFiles || 'C:\\Program Files') + // '\\7-Zip\\7z.exe' + '"' && "%ProgramFiles%\\7-Zip\\7z.exe" ]), rar : library_namespace.executable_file_path([ "rar", library_namespace.platform('windows') // WinRAR.exe && "%ProgramFiles%\\WinRAR\\rar.exe" ]) }, // 預設的壓縮程式。 default_program_type; function test_and_add_quoted_program(program_name, need_every) { if (Array.isArray(program_name)) { return program_name.every(function(program) { program = test_and_add_quoted_program(program); return !need_every || program; }); } var path = library_namespace.executable_file_path(program_name); if (!path) { return; } path = add_fso_path_quote(path); executable_file_path[program_name] = path; return path; } if (!executable_file_path['7z'] && library_namespace.platform('windows')) { // @see GitHub.updater.node.js // 嘗試取得7-Zip的執行路徑 // try to read 7z program path from Windows registry executable_file_path['7z'] = library_namespace.run_JScript( "var p7z_path='HKCU\\\\Software\\\\7-Zip\\\\Path';" // use stdout. 64 bit first. + "p7z_path=RegRead(p7z_path+64)||RegRead(p7z_path);" // `p7z_path` maybe `undefined` here. + "console.log(p7z_path&&add_quote(p7z_path));", { attach_library : true }); if (false) { console.log(executable_file_path['7z']); console.log('stdout: ' + executable_file_path['7z'].stdout.toString()); console.log('stdout: ' + JSON.parse(executable_file_path['7z'].stdout.toString())); console.log('stderr: ' + executable_file_path['7z'].stderr.toString()); } executable_file_path['7z'] = executable_file_path['7z'].stdout .toString().trim(); // 沒安裝 7-Zip 的情況,此時 `executable_file_path['7z'] === ''`。 if (executable_file_path['7z']) { executable_file_path['7z'] = executable_file_path['7z'] && JSON.parse(executable_file_path['7z']).trim() || ''; executable_file_path['7z'] = library_namespace .executable_file_path(executable_file_path['7z'] + '7z.exe'); // console.log(executable_file_path['7z']); executable_file_path['7z'] = add_fso_path_quote(executable_file_path['7z']); } else { // error: no 7z.exe delete executable_file_path['7z']; } // console.log(executable_file_path['7z']); } Object.keys(executable_file_path).forEach(function(program_type) { var program_path = executable_file_path[program_type]; // console.log(program_type + ': ' + program_path); if (program_path) { executable_file_path[program_type] // = add_fso_path_quote(program_path); if (!default_program_type) { // 挑選第一個可用的壓縮程式。 default_program_type = program_type; } } }); // console.log(executable_file_path); // 舊版本 7z 不能 rename,Unix 上有 zip 可替代,因此即使有了 7z,依然作個測試。 if (// !executable_file_path.rar && // 比較少存在的放第一個測試。 test_and_add_quoted_program([ 'zipnote', 'unzip', 'zip' ], true)) { // e.g., /usr/bin/zip Info-ZIP @ macOS, linux // 在 linux 下操作壓縮檔案的功能。 // Info-ZIP must use zipnote to rename file! // default_program_type = 'zip'; } // TODO: https://pureinfotech.com/compress-files-powershell-windows-10/ // ompress files using PowerShell // -------------------------------------------------------------------------------------------- function Archive_file(archive_file_path, options, callback) { if (!callback && typeof options === 'function') { // shift arguments. callback = options; options = null; } options = library_namespace.setup_options(options); this.archive_type = options.type; if (!this.archive_type) { var matched = archive_file_path.match(/\.([a-z\d\-_]+)$/i); if (matched) this.archive_type = matched[1].toLowerCase(); } this.program_type = options.program_type || this.archive_type; if (!executable_file_path[this.program_type]) { this.program_type = default_program_type; } if (!apply_switches[this.program_type]) { this.unknown_type = true; library_namespace.error([ 'Archive_file: ', { // gettext_config:{"id":"unknown-type-$1-please-install-$2"} T : [ 'Unknown type: %1, please install %2', this.program_type, default_program_type || 'file archiver' ] } ]); } // {String}this.program this.program = executable_file_path[this.program_type]; this.archive_file_path = this.program_type === '7z' // 即使在 Windows 下,採用 "\" 作路徑分隔可能造成 7-Zip "系統找不到指定的檔案"錯誤。 ? archive_file_path.replace(/\\/g, '/') : archive_file_path; // for is_Archive_file() // this.constructor = Archive_file; if (typeof callback === 'function') callback(this, null, this.unknown_type && new Error('UNKNOWN_TYPE')); } function is_Archive_file(value) { // return value && value.constructor === Archive_file; return value instanceof Archive_file; } Archive_file.is_Archive_file = is_Archive_file; // -------------------------------------------------------------- // 注意: 這邊添加引號的目的主要只是escape空白字元space "\u0020",不能偵測原先輸入中的引號! function add_fso_path_quote(arg) { if (library_namespace.is_Object(arg) && arg.Path) { arg = arg.Path; } if (!arg) return arg; if (typeof arg !== 'string') { library_namespace.error('add_fso_path_quote: ' + 'Should input string but get ' + (typeof arg) + ':'); console.error(arg); } return /^"(\\[\s\S]|[^\\\n])*"$/.test(arg) ? arg : // JSON.stringify() '"' + String(arg).replace(/"/g, '\\"') + '"'; } function remove_fso_path_quote(arg) { if (library_namespace.is_Object(arg) && arg.Path) { arg = arg.Path; } if (!arg) return arg; // JSON.parse() return /^".*"$/.test(arg) ? String(arg).slice(1, -1).replace( /\\(["'])/g, '$1') : arg; } // @inner function check_modify_time(switches, callback, FSO_list, operation, options) { if (false) { console.assert(operation === 'update' && !!options.only_when_newer_exists === true); } function check_time(FSO_list_newest_data) { var archive_file_data = library_namespace .get_newest_fso(this.archive_file_path); // console.trace([ FSO_list_newest_data, archive_file_data ]); if (FSO_list_newest_data && archive_file_data // && FSO_list_newest_data[0] < archive_file_data[0]) { // 當沒有新的檔案時直接跳出,可節省時間。 // e.g., CeL.application.net.work_crawler.ebook library_namespace.debug('沒有新的檔案,直接跳出。', 1, 'check_modify_time'); if (options.remove) { library_namespace.remove_file(FSO_list, true); } } else { library_namespace.debug('回歸正常操作 ' + operation + '。', 1, 'check_modify_time'); delete options.only_when_newer_exists; archive_file_execute.call(this, switches, callback, FSO_list, operation, options); } } Promise.resolve( library_namespace.get_newest_fso(FSO_list, options.only_when_newer_exists)).then( check_time.bind(this)); } function archive_file_execute(switches, callback, FSO_list, operation, options) { // console.trace([ switches, callback, FSO_list, operation, options ]); if (operation === 'update' && options.only_when_newer_exists) { check_modify_time.call(this, switches, callback, FSO_list, operation, options); return; } var command = [ this.program ], standard_input; if (Array.isArray(switches)) { standard_input = switches.standard_input; command.push(switches.join(' ')); } else if (library_namespace.is_Object(switches)) { // console.log(switches); for ( var switch_name in switches) { var value = switches[switch_name]; if (typeof value === 'function') { // allow modify FSO_list when zipnote renaming file value = value.call(this, FSO_list); } if (switch_name === 'program_path') { if (value in executable_file_path) value = executable_file_path[value]; command[0] = value; } else if (switch_name === 'standard_input') { standard_input = value; } else if (value !== undefined && value !== null) { // e.g., value === 0 command.push(value); } } } else { // assert: String|Number command.push(switches); } if (this.program_type === '7z' || this.program_type === 'rar') { // Stop switches parsing, stop switches scanning command.push('--'); } var operation_need_chdir = this.program_type === 'zip' && operation === 'update'; if (operation_need_chdir) { if (Array.isArray(FSO_list)) { FSO_list = FSO_list.map(remove_fso_path_quote); FSO_list.unshift(remove_fso_path_quote(this.archive_file_path)); } else { FSO_list = [ this.archive_file_path, FSO_list ] .map(remove_fso_path_quote); } } else command.push(add_fso_path_quote(this.archive_file_path)); var original_working_directory, using_working_directory; if (FSO_list) { if (!Array.isArray(FSO_list)) { FSO_list = [ FSO_list ]; } else if (operation_need_chdir) { // By default, zip will store the full path (relative to the // current directory). // 這裡的處置可以使壓縮檔案時: // zip a/b/zipfile.zip a/b/c/files // 讓 zipfile 中的路徑(path,name)只有 "c/files" // 這樣可使壓縮行為和7z相同。 var LCL = library_namespace .longest_common_starting_length(FSO_list); if (LCL > 0) { using_working_directory = FSO_list[0].slice(0, LCL) // assert: paths of FSO_list are not quoted .replace(/[^\\\/]+$/, ''); if (using_working_directory) { LCL = using_working_directory.length; FSO_list = FSO_list.map(function(path) { return path.slice(LCL); }); original_working_directory = process.cwd(); process.chdir(using_working_directory); } } } FSO_list = FSO_list.map(add_fso_path_quote).join(' '); if (this.program_type === '7z') { // 即使在 Windows 下,採用 "\" 作路徑分隔可能造成 7-Zip "系統找不到指定的檔案"錯誤。 FSO_list = FSO_list.replace(/\\/g, '/'); } command.push(FSO_list); } // console.trace(command); command = command.join(' '); library_namespace.debug({ // gettext_config:{"id":"working-directory-$1"} T : [ 'Working directory: %1', library_namespace.storage.working_directory() ] }, 1, 'archive_file_execute'); library_namespace.debug(command, 1, 'archive_file_execute'); try { var output = execSync(command, Object.assign({ input : standard_input }, options && options.exec_options)); if (original_working_directory) // recover working directory. process.chdir(original_working_directory); // console.log(output.toString()); if (typeof callback === 'function') { try { // 預防 callback throw callback(output); } catch (e) { if (false) { console.trace('archive_file_execute: ' // gettext_config:{"id":"callback-execution-error"} + 'Callback execution error!'); } library_namespace.error([ 'archive_file_execute: ', { // gettext_config:{"id":"callback-execution-error"} T : 'Callback execution error!' } ]); if (library_namespace.platform.nodejs) { console.error(e); } else { library_namespace.error(e); } } } return output; } catch (e) { if (original_working_directory) { // recover working directory. process.chdir(original_working_directory); } if (false) { console.trace('archive_file_execute: ' + this.program_type + ' execution error!'); } library_namespace.error([ 'archive_file_execute: ', { // gettext_config:{"id":"$1-execution-error"} T : [ '%1 execution error!', this.program_type ] } ]); if (library_namespace.platform.nodejs) { console.error(e); if (e.output) { var message = e.output.toString(), MAX_MESSAGE_LENGTH = 800; if (message.length > MAX_MESSAGE_LENGTH) { message = message.slice(0, MAX_MESSAGE_LENGTH) + '...'; } console.error(message); } } else { library_namespace.error(e); } if (typeof callback === 'function') callback(null, e); return e; } } // -------------------------------------------------------------- var FSO_list_operations = [ 'update', 'extract', 'remove', 'rename', 'verify' ], // default switches, modifiers // program_type: { command : { switches } } // 壓縮軟體 default_switches = { '7z' : { // add compress_list update : { // use "a" to allow -sdel switch command : 'a -sccUTF-8 -scsUTF-8', type : function() { return '-t' + this.archive_type; }, // recurse : '-r', level : '-mx=9', }, extract : { command : 'x' }, // delete remove : { command : 'd' }, // get archive information / status info : { command : 'l -slt -sccUTF-8' }, // 請注意: rename 必須先安裝 7-Zip **16.04 以上的版本**。 rename : { command : 'rn' }, // test verify : { command : 't' } }, // Info-ZIP http://infozip.sourceforge.net/ // https://linux.die.net/man/1/zip zip : { update : { // @ macOS // zip error: // Invalid command arguments (short option 'N' not supported) // (long option 'unicode' not supported) // unicode : '-UN=UTF8', // recurse : '-r', // Do not save extra file attributes such as “_MACOSX” or // “._Filename” and .ds store files. // 'no-extra' : '-X', level : '-9' }, extract : { program_path : executable_file_path.unzip }, // delete remove : { command : '-d' }, info : { // TODO: zipinfo program_path : executable_file_path.unzip, command : '-l -v' }, rename : { program_path : executable_file_path.zipnote, standard_input : function(FSO_list) { // console.log(this); // console.log(FSO_list); var args = '@ ' + FSO_list[0] + '\n' + '@=' + FSO_list[1]; FSO_list.truncate(); return args; }, command : '-w' }, // test verify : { command : '-T' } }, rar : { // TODO } }; var apply_switches_handler = { '7z' : { yes : function(value) { // assume Yes on all queries return '-y'; }, type : function(value) { return '-t' + value; }, level : function(value) { if (value === '') return '-mx'; if (value === 'max') value = 9; if (value >= 0) return '-mx=' + value; return; }, // Recurse subdirectories recurse : function(value) { if (value) return '-r' + (value === true ? '' : value); }, // delete files after compression: for 7-Zip > 18.01? remove : function(value) { if (value) return '-sdel'; }, // destination directory path, output directory output : function(value) { if (value) return add_fso_path_quote('-o' + value); }, // temp directory 設置臨時工作目錄。 work_directory : function(value) { return add_fso_path_quote('-w' + value); }, // additional switches extra : function(value) { if (value) return value; }, list_file : function(value) { if (value) return add_fso_path_quote('-i@' + value); } }, zip : { // delete files after compression remove : function(value) { if (value) // move into zipfile (delete files) return '-m'; }, level : function(value) { if (value === 'max') value = 9; if (value >= 0 && value <= 9) return '-' + value; return; }, // Recurse subdirectories recurse : function(value) { // recurse into directories if (value) return '-r'; } }, rar : { // TODO // additional switches extra : function(value) { if (value) return value; } } }; var apply_switches = { rar : null, '7z' : null, zip : null }; Object.keys(apply_switches).forEach(function(program_type) { if (apply_switches[program_type]) return; apply_switches[program_type] // apply_switches_handler = function(operation, options) { var is_original = true, // switches = default_switches[program_type][operation]; if (options) { for ( var switch_name in apply_switches_handler[program_type]) { if (switch_name in options) { if (is_original) { is_original = false; switches = Object.assign( // Object.create(null), switches); } switches[switch_name] // = apply_switches_handler[program_type][switch_name] // .call(this, options[switch_name]); } else if (false) { library_namespace.warn( // 'Switch unchanged [' + switch_name + ']'); } } } return switches; }; }); // -------------------------------------------------------------- // .info() 共同可用的屬性: // path size modified method // Lists contents of archive. function parse_7z_info_output(output) { // console.log(output && output.toString()); if (!output || !(output = output.toString())) { return output; } // console.log(output); // console.trace(this); // initialization this.information = undefined; // fso path hash this.fso_path_hash = Object.create(null); // fso_status_list, files of archive this.fso_status_list = []; // console.log(JSON.stringify(output)); // console.log(JSON.stringify(output.split(/\r?\n\r?\n/))); output.split(/\r?\n\r?\n/).forEach(function(FSO_data_lines) { // console.log(JSON.stringify(FSO_data_lines)); var FSO_data = Object.create(null); FSO_data_lines.split(/\r?\n|\r/).forEach(function(line) { var matched = line.match(/^([a-z\s]+)=(.*)$/i); if (matched) { matched[2] = matched[2].trim(); if (false && matched[2] === '-') { // e.g., .folder, .encrypted matched[2] = false; } // FSO_data[matched[1].trim().toLowerCase()] = FSO_data[matched[1].trim()] = matched[2]; } }); // console.log(FSO_data); FSO_data.is_folder // 7-Zip read .rar = FSO_data.Folder === '+' // 7-Zip read .7z || FSO_data.Attributes === 'D'; if (!FSO_data.Path) { ; } else if (this.information) { this.fso_status_list.push(FSO_data); if (this.fso_path_hash[FSO_data.Path]) { CeL.warn('Duplicate FSO path: ' + FSO_data.Path); } // FSO status hash get from archive_file.info() // archive_file.fso_path_hash = { FSO path : {FSO data}, ... } this.fso_path_hash[FSO_data.Path] = FSO_data; } else { // assert: the first item is the archive file itself // archive_file.information = { archive information } this.information = FSO_data; // 對於壓縮檔案應該有的大小 'Physical Size' 不同於真正大小的情況, // 'Tail Size' 會記錄著壓縮檔案之後的尾端大小, // ['Physical Size']+.offset+['Tail Size']=壓縮檔案真正的大小。 } }, this); return this.fso_path_hash; } // TODO: 警告: macOS 底下,無法讀取非 latin 字元! // https://github.com/nodejs/node/issues/2165 // https://marcosc.com/2008/12/zip-files-and-encoding-i-hate-you/ // Mac OS HFS+ use UTF-8 NFD, UTF-8-MAC // Windows or Linux will preserve and return NFC or NFD // 採用 output.normalize('NFD') 這個方法無效。 function parse_zip_info_output(output) { // console.log(output && output.toString()); if (!output || !(output = output.toString())) { return output; } // console.log(output); // console.trace(this); // initialization this.information = undefined; // fso path hash this.fso_path_hash = Object.create(null); // fso_status_list, files of archive this.fso_status_list = []; // console.log(JSON.stringify(output)); // console.log(JSON.stringify(output.split(/\r?\n\r?\n/))); output = output.split(/\r?\n/); // "Archive: zipfile.zip" output.shift(); // " Length Method Size Ratio Date Time CRC-32 Name" var headers = output.shift().trim().toLowerCase().split(/\s+/), // PATTERN = new RegExp('^\\s*' + '([^\\s]+)\\s+'.repeat(headers.length - 1) + '(.+)$'); if (headers.at(-1) === 'name') { // 這邊應該會被執行到,否則恐怕是不一樣版本的zip,無法解析。 headers[headers.length - 1] = 'path'; } output.forEach(function(FSO_data_line) { // console.log(JSON.stringify(FSO_data_line)); if (FSO_data_line.startsWith('--')) return; var matched = FSO_data_line.match(PATTERN); if (!matched) return; var FSO_data = Object.create(null); matched.shift(); matched.forEach(function(data, index) { FSO_data[headers[index]] = data; }); FSO_data.modified = FSO_data.date + ' ' + FSO_data.time; // console.log(FSO_data); if (!FSO_data.Path) { ; } else { this.fso_status_list.push(FSO_data); if (this.fso_path_hash[FSO_data.Path]) { CeL.warn({ // gettext_config:{"id":"duplicate-fso-path-$1"} T : [ 'Duplicate FSO path: %1', FSO_data.Path ] }); } // FSO status hash get from archive_file.info() // archive_file.fso_path_hash = { FSO path : {FSO data}, ... } this.fso_path_hash[FSO_data.Path] = FSO_data; } }, this); return this.fso_path_hash; } var postfix = { '7z' : { info : parse_7z_info_output }, zip : { info : parse_zip_info_output } } // -------------------------------------------------------------- /** * @inner */ function archive_file_operation(operation, options, callback, FSO_list) { if (!callback && typeof options === 'function') { // shift arguments. callback = options; options = null; } if (!default_switches[this.program_type][operation]) { var error = { // gettext_config:{"id":"$1-does-not-provide-this-feature-$2"} T : [ '%1 未提供這種功能:%2', this.program_type, operation ] }; if (operation !== 'rename') { library_namespace.error(error); } else { library_namespace.debug(error, 1, 'archive_file_operation'); } // TODO: Localization error = this.program_type + ' has no operation: ' + operation; error = new Error(error); callback && callback(null, error); return error; } options = library_namespace.setup_options(options); var original_working_directory, original_archive_file_path; if (options.cwd) { // change working directory. e.g., 進入到壓縮檔所在的目錄來解壓縮。 var using_working_directory = options.cwd, using_archive_file; if (is_Archive_file(using_working_directory)) { library_namespace.debug({ // gettext_config:{"id":"operate-$1-in-the-directory-where-the-archive-is-located"} T : [ '在壓縮檔所在目錄下操作 %1。', operation ] }, 1, 'archive_file_operation'); using_archive_file = using_working_directory; using_working_directory = using_working_directory.archive_file_path .replace(/[^\\\/]+$/, ''); } using_working_directory = using_working_directory.replace( /^(\.[\\\/])+/, '').replace(/[\\\/]+$/, ''); if (using_working_directory && using_working_directory !== '.' && library_namespace .directory_exists(using_working_directory)) { original_working_directory = process.cwd(); if (original_working_directory === using_working_directory) { original_working_directory = null; } else { library_namespace.debug({ // gettext_config:{"id":"changing-working-directory-$1-→-$2"} T : [ 'Changing working directory: [%1]→[%2]', original_working_directory, using_working_directory ] }, 1, 'archive_file_operation'); process.chdir(using_working_directory); } } if (using_archive_file) { if (original_working_directory) { original_archive_file_path = using_archive_file.archive_file_path; using_archive_file.archive_file_path = using_archive_file.archive_file_path .match(/[^\\\/]+$/)[0]; } else { using_archive_file = null; } } } var _this = this, switches = apply_switches[this.program_type].call( this, operation, options), // _postfix = postfix[this.program_type] && postfix[this.program_type][operation]; delete options.exec_options; // @see archive_file_execute() var output_or_error = this.execute(switches, // callback && _postfix ? function(output, error) { // console.log(output.toString()); callback(_postfix.call(_this, output), error); } : callback, FSO_list, operation, options); if (original_working_directory) { // recover working directory. process.chdir(original_working_directory); using_archive_file.archive_file_path = original_archive_file_path; } return _postfix ? _postfix.call(this, output_or_error) : output_or_error; } Archive_file.prototype = { // default switches switches : default_switches, execute : archive_file_execute }; Object.keys(default_switches['7z']).forEach(function(operation) { if (!Archive_file.prototype[operation]) { Archive_file.prototype[operation] // = FSO_list_operations.includes(operation) // archive_file_wrapper_with_FSO_list ? function(FSO_list, options, callback) { if (library_namespace.is_Object(FSO_list) // treat FSO_list as options && !library_namespace.is_Object(options)) { // shift arguments. callback = options; options = FSO_list; FSO_list = null; } return archive_file_operation.call(this, operation, // options, callback, FSO_list); } // : function archive_file_wrapper(options, callback) { return archive_file_operation.call(this, operation, // options, callback); }; } }); // -------------------------------------------------------------- // is relative path 為相對路徑 function is_relative_path(path) { // e.g., '/usr' return !path.startsWith('/') // e.g., 'C:\\' && !path.includes(':\\') // e.g., 'C:' && !path.endsWith(':'); } // 進到指定目錄下壓縮檔案。這個方法可以可以避免壓縮檔包含目錄前綴 prefix。 // 注意: 這個方法會改變工作目錄! 因此不能用非同步 async 的方法。 function archive_under(source_directory, archive_file_path, options) { if (typeof options === 'string') { options = { // type : 'zip', files : options }; } else { // archive_options options = library_namespace.setup_options(options); } // https://www.7-zip.org/faq.html // 7-Zip stores only relative paths of files without drive letter prefix var original_working_directory = library_namespace.storage .working_directory(); // 注意: source_directory 前後有空白時會出問題。 library_namespace.storage.working_directory(source_directory); if (typeof archive_file_path === 'string' && is_relative_path(archive_file_path)) { // archive_file_path 為相對 original_working_directory 之 path。 // 因為工作目錄已經改變,必須將 archive_file_path 改成絕對目錄。 archive_file_path = library_namespace.append_path_separator( original_working_directory, archive_file_path); } var archive_file = is_Archive_file(archive_file_path) ? archive_file_path : new Archive_file(archive_file_path, options), // files_to_archive = options.files || '.', list_file_name, list_file_path; if (Array.isArray(files_to_archive) && files_to_archive.join('" "').length > 7800) { list_file_name = '!list_file.tmp.lst'; if (false) { // Now under source_directory list_file_path = library_namespace.append_path_separator( source_directory, list_file_name); } list_file_path = list_file_name; library_namespace.info([ archive_under.name + ': ', { T : '檔案列表過長,寫入列表檔 [' + list_file_path + '] 以壓縮。' } ]); if (library_namespace.write_file(list_file_path, files_to_archive .join('\n'))) { library_namespace.error([ archive_under.name + ': ', { T : [ '檔案列表過長,寫入列表檔案失敗: %1', list_file_path ] } ]); list_file_name = list_file_path = null; } else { options.list_file = list_file_path; files_to_archive = []; delete options.files; if (false) { console.trace([ library_namespace.storage.working_directory(), source_directory, options ]); } } } archive_file.update(files_to_archive, options); if (list_file_name) { library_namespace.remove_file(list_file_path); } // recover working directory. library_namespace.storage.working_directory(original_working_directory); return archive_file; } Archive_file.archive_under = archive_under; // -------------------------------------------------------------- // setup executable file path + default switches // CeL.application.storage.archive.WinRAR // CeL.application.storage.archive.7_zip // @see CeL.application.OS.Windows.archive // var archive_file; // -------------------------------------------------------------------------------------------- // export 導出. Object.assign(Archive_file, { add_fso_path_quote : add_fso_path_quote, remove_fso_path_quote : remove_fso_path_quote, // 給外部程式設定壓縮執行檔路徑使用。 executable_file_path : executable_file_path, // read-only default_program_type : default_program_type }); return Archive_file; }