Files
rappaurio-sae501_502/app/node_modules/cejs/data/file.js
2023-09-25 13:27:24 +02:00

1701 lines
46 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @name CeL file system functions
* @fileoverview 本檔案包含了 file system functions。
* @since 2013/1/5 9:38:34
* @see <a href="http://en.wikipedia.org/wiki/Filesystem" accessdate="2013/1/5
* 9:44">File system</a>
*/
'use strict';
// --------------------------------------------------------------------------------------------
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
typeof CeL === 'function' && CeL.run({
// module name
name : 'data.file',
// includes() @ data.code.compatibility.
require : 'data.code.compatibility.|application.OS.Windows.new_COM'
//
+ '|data.code.thread.Serial_execute',
// 設定不匯出的子函式。
no_extend : '*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
// requiring.
var new_COM = this.r('new_COM'), Serial_execute = this.r('Serial_execute');
// ---------------------------------------------------------------------//
// 基本宣告與定義。
var
// cache.
path_separator = library_namespace.env.path_separator,
/**
* FileSystemObject
*
* @inner
* @ignore
* @see <a
* href="http://msdn.microsoft.com/en-us/library/z9ty6h50(v=VS.84).aspx"
* accessdate="2010/1/9 8:10">FileSystemObject Object</a>, Scripting
* Run-Time Reference/FileSystemObject
* http://msdn.microsoft.com/en-US/library/hww8txat%28v=VS.84%29.aspx
*/
FSO = new_COM("Scripting.FileSystemObject");
// 可 test FSO.
var
// const flag enumeration: 用於指示 type.
// assert: (!!type) MUST true!
FILE = 1, FOLDER = 2,
// const flag enumeration: file/folder callback index.
FILE_HANDLER_INDEX = 0, FOLDER_HANDLER_INDEX = 1,
// const: sub files.
// 必須是不會被用於目錄名之值。
FILES = '',
// TODO: file/directory status/infomation, even contents.
// 必須是不會被用於目錄名之值。
DATA = '.',
// 預設最多處理之 folder 層數。
// directory depth limit.
default_depth = 64,
// const flag enumeration: default property data.
fso_property = {
file : {
Attributes : '',
DateCreated : '',
DateLastAccessed : '',
DateLastModified : '',
Drive : '',
Name : '',
ParentFolder : '',
Path : '',
ShortName : '',
ShortPath : '',
Size : '',
Type : ''
},
folder : {
Attributes : '',
DateCreated : '',
DateLastAccessed : '',
DateLastModified : '',
Drive : '',
Name : '',
ParentFolder : '',
Path : '',
ShortName : '',
ShortPath : '',
Size : '',
Type : '',
Files : '',
IsRootFolder : '',
SubFolders : ''
},
driver : {
AvailableSpace : '',
DriveLetter : '',
DriveType : '',
FileSystem : '',
FreeSpace : '',
IsReady : '',
Path : '',
RootFolder : '',
SerialNumber : '',
ShareName : '',
TotalSize : '',
VolumeName : ''
}
};
// a network drive.
// http://msdn.microsoft.com/en-us/library/ys4ctaz0(v=vs.84).aspx
// NETWORK_DRIVE = 3,
/**
* 取得指定 path 之檔名/資料夾名稱。
*
* @param {String}path
* 指定之目標路徑。
* @returns {String} 檔名/資料夾名稱。
* @inner
*/
function name_of_path(path) {
var match = typeof path === 'string' && path.match(/[^\\\/]+/);
return match && match[0] || path || '';
}
/**
* 傳回新的資料夾結構。
*
* @returns 新的資料夾結構。
* @inner
*/
function new_folder() {
var folder = Object.create(null);
// 檔案, sub-files.
folder[FILES] = Object.create(null);
// directory status/infomation.
folder[DATA] = Object.create(null);
return folder;
}
// ---------------------------------------------------------------------//
// filter 處理。
/**
* options 所使用到的 filter name。
*/
var regular_filter_name = {
// path filter.
// 通常我們輸入的只會指定 path filter。
filter : 0,
// file name filter. 篩選檔案.
// WARNING: 若有設定,只要檔名不符合,即使 folder name 符合亦一樣會被剔除!
file_filter : 1,
// folder name filter. 篩選資料夾.
folder_filter : 2
};
/**
* 判別是否為可接受之字串篩選器。<br />
* filter should has NO global flag.
*
* @param {undefined|String|RegExp|Function}filter
* 字串篩選器。
*
* @returns {Boolean} 為可接受之字串篩選器。
*/
function is_regular_filter(filter) {
return !filter || typeof filter === 'string'
// RegExp
|| library_namespace.is_RegExp(filter)
// function
|| typeof filter === 'function';
}
/**
* 檢測 options 的 filter。
*
* @param {Object}options
* optional flag. e.g., filter.
*/
function check_filter_of_options(options) {
for ( var filter_name in regular_filter_name)
if ((filter_name in options)
&& !is_regular_filter(options[filter_name]))
delete options[filter_name];
}
/** {Number}未發現之index。 const: 基本上與程式碼設計合一,僅表示名義,不可更改。(=== -1) */
var NOT_FOUND = ''.indexOf('_');
/**
* 判斷指定字串是否合乎篩選。
*
* @param {undefined|String|RegExp|Function}filter
* 字串篩選器。
* @param {String}string
* 欲測試之指定字串。string to test.
* @param [argument]
* 當篩選器為 function 時之附加引數。
*
* @returns {Boolean} 為可接受之字串篩選器。
*/
function match_filter(filter, string, argument) {
return !filter
//
|| (typeof filter === 'string'
// String
? string.includes(filter)
// RegExp
: filter.test ? filter.test(string)
// function
: filter(string, argument));
}
// ---------------------------------------------------------------------//
// 檔案系統模擬結構。
/**
* 建立模擬檔案系統結構 (file system structure) 之 Class。取得檔案列表。<br />
* 注意: 在此設定的 callback不具有防呆滯功能。<br />
* TODO: fs.readdir or fs.readdirSync @ node.js<br />
* TODO: follow link
*
* @example <code>
// 列出指定目錄下所有壓縮檔。
CeL.run('data.file', function() {
var folder = new CeL.file_system_structure('D:\\a', { file_filter : /\.(zip|rar|7z|exe)$/i });
folder.each(function(fso, info) {
CeL[info.is_file ? 'log' : 'info']([ info.index, '/', info.length, '[', fso.Path, ']' ]); }, {
// filter : 'f',
max_count : 5,
'final' : function() { CeL.log([ this.count.filtered_file, ' done']); }
}
);
});
</code>
*
* @param {String|Array}path
* 指定之目標路徑。<br />
* 使用相對路徑,如 '..' 開頭時,須用 get_file_path() 調整過。
* @param {Object}[options]
* optional flag. e.g., filter.
*
* @constructor
*
* @see <a
* href="http://msdn.microsoft.com/library/en-US/script56/html/0fa93e5b-b657-408d-9dd3-a43846037a0e.asp">FileSystemObject</a>
*
* @since 2013/1/6 18:57:16 堪用。
*/
function file_system_structure(path, options) {
if (this === file_system_structure)
throw 'Please use "new file_system_structure()"'
+ ' instead of "file_system_structure()"!';
// private properties.
this.structure = new_folder();
this.count = Object.create(null);
this.path_list = [];
this.add(path, options);
}
/**
* 解析/取得模擬結構中,指定 path 所在位置。
*
* @param {String}path
* 指定之目標路徑。
* @param {Number}create_type
* 以 FILE or FOLDER 創建此標的。
*
* @returns {Object} 檔案系統模擬結構中,指定 path 所在位置。
* @returns undefined error occurred.
*/
function resolve_path(path, create_type) {
var base = this.structure;
if (path && typeof path === 'string') {
var name, i = 0, list = library_namespace.simplify_path(path)
.replace(/[\\\/]+$/, '');
if (name = list.match(/^\\\\[^\\]+/)) {
// a network drive.
name = name[0];
list = list.slice(name.length).split(/[\\]+/);
list[0] = name;
} else
list = list.split(/[\\\/]+/);
for (; i < list.length; i++) {
name = list[i];
if (name in base)
base = base[name];
else if (!create_type)
return undefined;
else if (create_type === FOLDER
// 不是最後一個。
|| i + 1 < list.length)
base = base[name] = new_folder();
else
base[FILES][name] = null;
}
}
return base;
}
/**
* 將 fso 所有 data_fields 中的項目 extend 到 data 中。
*
* @inner
* @private
*
* @param {file_system_object}fso
* file system object.
* @param {Object}data_fields
* 欲 extend 之項目。
* @param {檔案系統模擬結構}data
* 所在位置。
*
* @returns {Number} extend 之項目數。
*/
function fill_data(fso, data_fields, data) {
var name,
// {Number} extend 之項目數。
// count = 0,
item;
for (name in data_fields)
try {
// Get the infomation/status of fso.
if ((item = fso[name]) !== undefined) {
data[name]
//
= typeof data_fields[name] === 'function'
//
? data_fields[name](item) : item;
// count++;
}
} catch (e) {
// 在取得 RootFolder 的 .DateCreated,
// .DateLastAccessed, .DateLastModified 時,會
// throw。
if (library_namespace.is_debug(3)) {
library_namespace.warn('fill_data: 擷取資訊 [' + name
+ '] 時發生錯誤!');
library_namespace.error(e);
}
}
// return count;
return data;
}
/**
* 將指定 path 加入模擬結構。<br />
* 注意: base path 本身不受 filter 限制!
*
* @param {String|Array}path
* 指定之目標路徑。<br />
* 使用相對路徑,如 '..' 開頭時,須用 get_file_path() 調整過。
* @param {Object}[options]
* optional flag. e.g., filter.
*/
function add_path(path, options) {
library_namespace.debug('初始化+正規化。', 2, 'add_path');
// 前置處理。
if (!library_namespace.is_Object(options))
options = Object.create(null);
if (isNaN(options.depth) || options.depth < 0
// || options.depth > default_depth
)
options.depth = default_depth;
check_filter_of_options(options);
var callback_Array;
if ('callback' in options) {
callback_Array = options.callback;
if (typeof callback_Array === 'function')
options.callback = callback_Array = [ callback_Array,
callback_Array ];
else if (!Array.isArray(callback_Array)
|| callback_Array.length === 0) {
delete options.callback;
callback_Array = undefined;
}
}
// for Array [path].
if (Array.isArray(path)) {
path.forEach(function(p) {
this.add(p, options);
}, this);
return;
}
var base = this.structure;
if (!path) {
library_namespace.debug(
// 省略 path 會當作所有 Drivers。
'取得各個 driver code。', 2, 'add_path');
for (var driver, drivers = new Enumerator(FSO.Drives);
//
!drivers.atEnd(); drivers.moveNext()) {
driver = drivers.item();
// http://msdn.microsoft.com/en-us/library/ts2t8ybh(v=vs.84).aspx
if (driver.IsReady)
base[driver.Path] = new_folder();
else
library_namespace.warn('add_path: Drive [' + driver.Path
+ '] 尚未就緒!');
}
return;
}
var fso,
// 類型
type;
// 轉換輸入之 path 成 FSO object。
try {
if (typeof path === 'string') {
// 注意: 輸入 "C:" 會得到 C: 的工作目錄。
if (FSO.FolderExists(path)) {
fso = FSO.GetFolder(path);
type = FOLDER;
} else if (FSO.FileExists(path)) {
fso = FSO.GetFile(path);
type = FILE;
}
} else if (typeof path === 'object' && path.Path) {
fso = isNaN(path.DriveType) ? path : FSO.GetFolder(path.Path);
type = fso.SubFolders ? FOLDER : FILE;
}
} catch (e) {
library_namespace.error(e);
}
if (typeof fso !== 'object' || !(path = fso.Path)) {
library_namespace.warn('add_path: 無法判別 path [' + path
+ ']!指定的文件不存在?');
return;
}
var list = this.path_list;
for (var i = 0; i < list.length; i++)
if (list[i].startsWith(path)) {
library_namespace.debug('已處理過 path [' + path + ']。', 2,
'add_path');
return;
}
library_namespace.debug(
//
'Adding [' + path + ']', 2, 'add_path');
list.push(path);
if (base = callback_Array && callback_Array[
//
type === FOLDER ? FOLDER_HANDLER_INDEX : FILE_HANDLER_INDEX])
try {
base(fso, {
depth : 0,
is_file : type !== FOLDER
});
} catch (e) {
library_namespace.error(e);
}
base = this.get(path, type);
var filter = options.filter,
//
file_filter = options.file_filter,
//
folder_filter = options.folder_filter,
//
folder_first = !!options.folder_first,
//
count = this.count,
// 注意: assert(undefined|0===0)
total_size = count.size | 0,
//
total_file_count = count.file | 0,
//
total_folder_count = count.folder | 0,
//
filtered_total_file_count = count.filtered_file | 0,
//
filtered_total_folder_count = count.filtered_folder | 0,
//
file_data_fields = library_namespace.is_Object(options.data)
//
? options.data : options.data === true ? fso_property.file : false,
//
folder_data_fields = library_namespace.is_Object(options.folder_data) ? options.folder_data
//
: options.folder_data === true || options.data === true
//
? fso_property.folder : file_data_fields,
// 巡覽/遍歷子目錄與所包含的檔案。
traverse = function(fso, base, depth) {
var item, collection, name, callback,
//
folder_data = base[DATA],
// 有無加入 file count 功能,在 JScript 10.0.16438 差別不到 4%。
// 為求方便,不如皆加入。
size = 0,
//
file_count = 0, folder_count = 0,
//
filtered_file_count = 0, filtered_folder_count = 0,
// list files of folder. 所包含的檔案.
each_file = function() {
library_namespace.debug('巡覽 [' + fso.Path + '] 之 sub-files。',
3, 'add_path');
try {
for (collection = new Enumerator(fso.Files);
//
!collection.atEnd(); collection.moveNext()) {
item = collection.item();
file_count++;
size += item.Size;
if (match_filter(filter, item.Path, depth)
&& match_filter(file_filter, name = item.Name,
depth)) {
++filtered_file_count;
library_namespace.debug(
//
'Adding sub-file [' + filtered_file_count + '/'
+ file_count + '] [' + item.Path + ']', 4,
'add_path');
base[FILES][name] = file_data_fields
//
? fill_data(item, file_data_fields, Object
.create(null)) : null;
// 預防 callback 動到 item排在最後才處理。
if (callback = callback_Array
//
&& callback_Array[FILE_HANDLER_INDEX])
try {
callback(item, {
depth : depth,
is_file : true
});
} catch (e) {
library_namespace.error(e);
}
}
}
} catch (e) {
// TODO: handle exception
}
total_size += size;
total_file_count += file_count;
filtered_total_file_count += filtered_file_count;
},
// 處理子目錄。
each_folder = function() {
library_namespace.debug('巡覽 [' + fso.Path + '] 之 sub-folders。',
3, 'add_path');
// 執行途中可能更動/刪除到此項目。try: 以防 item 已經被刪除。
try {
// 因為檔案可能因改名等改變順序,因此用 .moveNext()
// 的方法可能有些重複,有些漏掉未處理。
for (collection = new Enumerator(fso.SubFolders);
//
!collection.atEnd(); collection.moveNext()) {
item = collection.item();
folder_count++;
if (match_filter(filter, item.Path, depth)
//
&& match_filter(folder_filter, name = item.Name, depth)) {
filtered_folder_count++;
library_namespace.debug(
//
'Adding sub-folder [' + filtered_folder_count + '/'
+ folder_count + '] [' + item.Path + ']',
4, 'add_path');
name = base[name] = new_folder();
if (callback = callback_Array
//
&& callback_Array[FOLDER_HANDLER_INDEX])
try {
callback(item, {
depth : depth,
is_file : false
});
} catch (e) {
library_namespace.error(e);
}
if (depth < options.depth)
traverse(item, name, depth);
}
}
} catch (e) {
// TODO: handle exception
}
// 可加上次一層: 會連這次一層之檔案都加上去。
total_folder_count += folder_count;
filtered_total_folder_count += filtered_folder_count;
};
if (folder_data_fields)
fill_data(fso, folder_data_fields, folder_data);
// 自身已經處理完,接下來 sub-files/sub-folders 之 depth 皆 +1。
depth++;
if (folder_first) {
each_folder();
each_file();
} else {
each_file();
each_folder();
}
library_namespace.debug('巡覽 [' + fso.Path + ']: ' + file_count
+ '/' + total_file_count + ' files, ' + size + '/'
+ total_size + ' bytes, ' + folder_count + '/'
+ total_folder_count + ' folders。', 2, 'add_path');
folder_data.count = {
size : size,
file : file_count,
folder : folder_count,
filtered_file : filtered_file_count,
filtered_folder : filtered_folder_count
};
};
if (type === FOLDER) {
if (options.depth > 0) {
library_namespace.debug('開始巡覽 [' + path + ']。', 2, 'add_path');
traverse(fso, base, 0);
}
count.size = total_size;
count.file = total_file_count;
// +1: base path 本身。
count.folder = total_folder_count + 1;
count.filtered_file = filtered_total_file_count;
count.filtered_folder = filtered_total_folder_count + 1;
} else {
count.size = total_size + fso.Size;
// +1: base path 本身。
count.file = total_file_count + 1;
count.folder = total_folder_count;
// +1: base path 本身。
count.filtered_file = filtered_total_file_count + 1;
count.filtered_folder = filtered_total_folder_count;
}
}
/**
* 重新整理/同步模擬結構。
*
* @param {String|Array}path
* 指定之目標路徑。
* @param {Object}[options]
* optional flag. e.g., filter.
*/
function refresh_structure(path, options) {
// TODO: 勿 reset。
this.structure = new_folder();
this.path_list.forEach(function(p) {
this.add(p, options);
}, this);
}
/**
* 當呼叫 JSON.stringify() 時的前置處理。
*/
function structure_to_JSON(key) {
// hierarchy:
// {
// FILES:[],
// DATA:{},
// 資料夾名稱(sub directory name):{}
// };
var structure = Object.create(null),
//
traverse = function(folder, base) {
if (folder[FILES].length)
base[FILES] = folder[FILES];
for ( var name in folder)
if (name !== FILES && name !== DATA)
traverse(folder[name], base[name] = Object.create(null));
};
traverse(this.structure, structure);
return structure;
}
// ---------------------------------------------------------------------//
// 巡覽/遍歷檔案系統模擬結構之功能。
/**
* 處理執行 callback 發生錯誤時的函數。
*
* @inner
* @private
*
* @since 2013/1/7 22:38:47。
*/
function callback_error_handler(controller, error_Object, options, stack) {
var message = [ '執行 callback 時發生錯誤!您可' ],
//
skip_error = options.skip_error;
if (skip_error)
message.push({
a : '停止執行',
href : '#',
onclick : function() {
controller.stop();
}
}, ',或者', {
// 忽略錯誤
a : '不再提醒錯誤',
href : '#',
onclick : function() {
delete options.skip_error;
}
});
else
message.push({
a : '重試',
href : '#',
onclick : function() {
stack.index--;
controller.start();
}
}, '、', {
a : '跳過並繼續執行',
href : '#',
onclick : function() {
controller.start();
}
}, ',或者', {
a : '忽略所有錯誤',
href : '#',
title : '★若忽略所有錯誤,之後發生錯誤將不會停止,而會直接忽略。',
onclick : function() {
options.skip_error = true;
controller.start();
}
});
message.push('。');
CeL.warn(message);
if (error_Object)
library_namespace.error(error_Object);
if (!skip_error) {
controller.stop();
return true;
}
}
/**
* 巡覽/遍歷檔案系統模擬結構時,實際處理的函數。
*
* @param {Array}LIFO_stack
* 處理堆疊。
* @param {file_system_structure}_this
* file_system_structure instance.
* @param {Array}callback_Array
* callback_Array[]
* @param {Object}options
* optional flag. e.g., filter.
*
* @inner
* @private
*
* @since 2013/1/6 18:57:16 堪用。
*/
function travel_handler(LIFO_stack, _this,
//
callback_Array, options) {
// 每個 folder 會跑一次 travel_handler。
// 處理順序:
// 處理/執行 folder 本身
// → expand sub-files
// → 處理/執行 sub-files (若檔案過多會跳出至下一循環處理)
// → expand sub-folders
// → 下一循環 from sub-folder[0]。
var stack, base_space, base_path,
//
path, name, depth, path_exists;
if (LIFO_stack.length > 0) {
var callback, index;
stack = LIFO_stack.at(-1);
// 若有設定 stack.path必以 path_separator 作結。
base_path = stack.path || '';
if (!stack.is_file)
while (stack.index < stack.length) {
name = stack[stack.index++];
if (FSO.FolderExists(path = base_path
//
+ name
// 若有設定 stack.path必以 path_separator 作結。
+ path_separator)) {
base_path = path;
path_exists = true;
base_space = stack.base[name];
library_namespace.debug('處理/執行 folder [' + path
+ '] 本身。', 2, 'travel_handler');
// 判別是否在標的區域內。
// depth: 正處理之 folder 本身的深度。
if (isNaN(depth = stack.depth))
if (stack.length > 1) {
stack.depth = depth = 0;
} else
for (var i = 0,
//
path_list = _this.path_list;
//
i < path_list.length; i++)
if (path.startsWith(path_list[i])) {
stack.depth = depth = 0;
break;
}
if (!isNaN(depth)) {
// 無論有無 match filter皆應計數。
index = options.single_count
//
? options.index++ : options.folder_index++;
if ((callback
//
= callback_Array[FOLDER_HANDLER_INDEX])
&& match_filter(options.filter, path, depth)
&& match_filter(options.folder_filter,
name, depth))
try {
/**
* 處理/執行資料夾本身。 用 folder.Size==0 可判別是否為 empty
* folder。
*
* @see <a
* href="http://msdn.microsoft.com/en-us/library/1c87day3(v=vs.84).aspx"
* accessdate="2013/1/5 12:43">Folder
* Object</a>
*/
callback.call(this, FSO.GetFolder(path), {
is_file : false,
depth : depth,
data : base_space[DATA],
index : index,
length : options.folder_count
});
} catch (e) {
if (callback_error_handler(this, e,
options, stack))
return;
}
}
// expand sub-files.
// TODO: check fso.SubFolders
if (callback_Array[FILE_HANDLER_INDEX]
&& depth < options.depth) {
stack = [];
for (name in
//
(stack.base = base_space[FILES]))
stack.push(name);
if (stack.length > 0) {
if (options.sort)
if (typeof options.sort
//
=== 'function')
stack.sort(options.sort);
else
stack.sort();
library_namespace.debug('開始處理 [' + base_path
+ '] 之' + stack.length + ' sub-files ['
+ stack + '].', 2, 'travel_handler');
stack.index = 0;
stack.path = path;
stack.depth = depth + 1;
stack.is_file = true;
LIFO_stack.push(stack);
}
}
// 預防有 sub-folder還是先 break;
break;
} else {
if (path.startsWith('\\\\')) {
// a network drive.
base_path = path;
path_exists = true;
} else
library_namespace.warn([ 'travel_handler: ',
'無法 access folder [', path, ']',
'或許是操作期間,檔案有所更動。您可能需要 refresh' ]);
}
}
// depth: 正處理之 files 的深度。
depth = stack.depth;
callback = callback_Array[FILE_HANDLER_INDEX];
if (stack.is_file) {
library_namespace.debug('處理/執行 folder [' + base_path
+ '] 的 sub-files。', 2, 'travel_handler');
// main loop of file.
for (var max_count = options.max_count,
//
filter = options.filter,
//
base = stack.base, pass_data = {
is_file : true,
depth : depth,
// data : stack.base[DATA],
// index : options.index,
length : options.count
};;)
if (stack.index < stack.length) {
name = stack[stack.index++];
if (match_filter(options.filter, path = base_path
+ name, depth)
&& match_filter(filter, name, depth)) {
/**
* 處理/執行 sub-files。
*
* @see <a
* href="http://msdn.microsoft.com/en-us/library/1ft05taf(v=vs.84).aspx"
* accessdate="2013/1/5 12:43">File Object</a>
*/
if (FSO.FileExists(path))
try {
// sub-file 存在,則 parent
// folder 亦存在。
path_exists = true;
pass_data.index = options.index++;
pass_data.data = base[name];
callback.call(this, FSO.GetFile(path),
pass_data);
if (--max_count === 0
//
&& stack.index < stack.length)
return;
} catch (e) {
if (callback_error_handler(this, e,
options, stack))
return;
}
else
library_namespace.warn([
'travel_handler: 檔案 [', path,
'] 不存在或無法 access', '或許是操作期間,檔案有所更動。',
'您可能需要 refresh' ]);
}
// 無論有無 match filter皆應計數。
pass_data.index++;
} else {
// 去掉 sub-files 之stack。
LIFO_stack.pop();
options.index = pass_data.index;
break;
}
}
library_namespace.debug('已處理過 folder [' + base_path
+ '] 本身與 sub-files。expand sub-folders.', 2,
'travel_handler');
stack = LIFO_stack.at(-1);
// depth: 正處理之 folder 本身的深度。
depth = stack.depth;
if (!base_space) {
base_space = stack.base[stack[stack.index - 1]];
library_namespace.debug(
'可能因為 file list 長度超過 options.max_count已經被截斷過因此需要重設 base_space ['
+ stack[stack.index - 1] + '] → ' + base_space
+ '。', 2, 'travel_handler');
}
} else {
base_path = '';
base_space = _this.structure;
}
if (isNaN(depth) || depth < options.depth) {
if (LIFO_stack.length === 0
// 確認所在的 folder 存在。
// 若不存在,也毋須 expand 了。
|| path_exists
// 亦可以下列檢測 base_path 的方式判別。
// || base_path !== LIFO_stack[LIFO_stack.length -
// 1].path
) {
library_namespace.debug('expand sub-folders.', 3,
'travel_handler');
for (name in ((stack = []).base = base_space))
if (name !== FILES && name !== DATA)
stack.push(name);
if (stack.length > 0) {
if (options.sort)
if (typeof options.sort === 'function')
stack.sort(options.sort);
else
stack.sort();
// sub-folders / sub-directory.
library_namespace.debug([ '開始處理 [', base_path, '] 之 ',
stack.length, ' 個子資料夾 [<span style="color:#25a;">',
stack.join('<b style="color:#47e;">|</b>'),
'</span>].' ], 2, 'travel_handler');
stack.index = 0;
stack.path = base_path;
if (!isNaN(depth))
stack.depth = depth + 1;
LIFO_stack.push(stack);
return;
}
}
if (!base_path) {
// 應該只有 structure 為空時會用到。
library_namespace.warn(
//
'travel_handler: structure 為空?');
return this.finish();
}
}
// 檢查本 stack 是否已處理完畢。
while ((stack = LIFO_stack.at(-1))
//
.index === stack.length) {
library_namespace.debug('Move up. stack.length = '
+ LIFO_stack.length, 2, 'travel_handler');
LIFO_stack.pop();
if (LIFO_stack.length === 0)
return this.finish();
}
}
/**
* travel structure.<br />
* 巡覽/遍歷檔案系統模擬結構的 public 公用函數。
*
* TODO:<br />
* 不區分大小寫。ignore case<br />
* 與 dir/a/s 相較network drive 的速度過慢。
*
* @param {Function|Array}callback
* file system handle function / Array [file handler, folder
* handler].<br />
* 可以安排僅對folder或是file作用之function。<br />
* handle function 應有的長度: 2<br />
* callback(fso, info = {is_file, depth, data : fso data, index,
* length}); index = 0 to (length - 1)
* @param {Object}[options]
* optional flag. e.g., filter.<br />
* options 之內容會被改動!
*
* @returns {Serial_execute} controller
* @returns undefined error occurred.
*
* @since 2013/1/6 18:57:16 堪用。
*/
function for_each_FSO(callback, options) {
var path_length = this.path_list.length;
if (path_length === 0) {
if (library_namespace.is_debug())
library_namespace.warn(
//
'for_each_FSO: 尚未設定可供巡覽之 path。');
return undefined;
}
library_namespace.debug('初始化+正規化。', 2, 'for_each_FSO');
if (typeof callback === 'function')
callback = [ callback, callback ];
else if (!Array.isArray(callback) || callback.length === 0)
return;
// 前置處理。
if (!library_namespace.is_Object(options))
options = Object.create(null);
// 計數 + 進度。
options.index = 0;
// 至此 options.count 代表 file count.
options.count = this.count.filtered_file;
options.folder_count = this.count.filtered_folder;
if (options.single_count
//
= callback[FOLDER_HANDLER_INDEX]
//
=== callback[FILE_HANDLER_INDEX])
options.folder_count
//
= (options.count += options.folder_count);
else
options.folder_index = 0;
check_filter_of_options(options);
if (typeof options.sort !== 'function'
&& !(options.sort = !!options.sort))
delete options.sort;
// 限定 traverse 深度幾層。深入/不深入下層子目錄及檔案。
if (isNaN(options.depth) || (options.depth |= 0) < 0)
options.depth = default_depth;
if (isNaN(options.max_count) || (options.max_count |= 0) < 1
|| options.max_count > 1e5)
// 預設一次 thread 最多處理之檔案個數。
options.max_count = 800;
options.argument = [ [], this, callback, options ];
if (typeof options.final === 'function')
options.final = options.final.bind(this);
library_namespace.debug('開始巡覽 ' + path_length + ' paths。', 2,
'for_each_FSO');
return new Serial_execute(travel_handler, options);
}
// ---------------------------------------------------------------------//
// public interface of file_system_structure.
Object.assign(file_system_structure.prototype, {
get : resolve_path,
add : add_path,
each : for_each_FSO,
refresh : refresh_structure,
list : function(options) {
var list = [];
// TODO: 可用萬用字元。
this.each(function(fso, info) {
if (info.is_file)
list.push(fso.Path);
}, options);
return list;
},
toJSON : structure_to_JSON
});
// ---------------------------------------------------------------------//
// CeL.run('data.file');
// var file_handler = new CeL.file([ 'path', 'path' ]);
// file_handler.merge('to_path').add('path').forEach(function(){});
function Files(path_list, options) {
// private property:
// this.data[path] = data of file = {
// encoding : encoding cache. NOT absolutely correct.
// other data.
// }
this.data = {};
// private property:
// this.order = ['path'];
if (options) {
if (typeof options.comparator === 'function')
// e.g., function(a, b){return a-b;};
this.comparator = options.comparator;
}
this.add(path_list, options);
}
function Files_add_object(path, data, order, filter, attributes) {
var object, count = 0;
// 注意: 輸入 "C:" 會得到 C: 的工作目錄。
if (FSO.FolderExists(path)) {
library_namespace.debug('Add folder [' + path + ']!', 2);
var fso = FSO.GetFolder(path), item, collection;
for (collection = new Enumerator(fso.Files);
//
!collection.atEnd(); collection.moveNext()) {
item = collection.item();
path = item.Path;
if (!match_filter(filter, path))
continue;
data[path] = object = {
size : item.Size
};
if (attributes)
Object.assign(object, attributes);
if (order)
order.push(path);
count++;
}
} else if (FSO.FileExists(path)) {
library_namespace.debug('Add file [' + path + ']!', 2);
if (match_filter(filter, path)) {
data[path] = object = {};
if (attributes)
Object.assign(object, attributes);
if (order)
order.push(path);
count++;
}
} else
library_namespace.warn('Files_add_object: 無法辨識 path [' + path
+ ']!');
library_namespace.debug('Get ' + count + ' files.', 2);
}
function Files_add(path_list, options) {
var data = this.data,
//
order = Array.isArray(this.order) && this.order,
//
filter = is_regular_filter(options.filter) && options.filter;
if (typeof path_list === 'string')
Files_add_object(path_list, data, order, filter);
else if (Array.isArray(path_list))
path_list.forEach(function(path) {
Files_add_object(path, data, order, filter);
});
else if (library_namespace.is_Object(path_list))
for ( var path in path_list)
Files_add_object(path, data, order, filter, path_list[path]);
else {
library_namespace.warn('Files: Unknown path list [' + path_list
+ ']!');
order = false;
}
if (order && this.comparator)
this.sort(
// default: this.comparator
);
return this;
}
// create and return this 之 list。
// 請勿直接使用 this.order而應該使用 this.list()
// 因為 this.order 可能尚未準備好。
// 不想改到 this 的,得自己 .slice()。
function Files_list(rebuilt) {
if (rebuilt || !Array.isArray(this.order)) {
var order = [];
// 重新創建 order。
for ( var path in this.data)
order.push(path);
this.order = order;
}
return this.order;
}
function Files_sort(comparator, options) {
// 重製 order。
var order = this.list(),
//
comparator = comparator || this.comparator;
if (options && options.no_set_order)
order = order.slice();
if (typeof use_function === 'function')
order.sort(use_function);
else if (library_namespace.is_RegExp(use_function))
order.sort(function(key_1, key_2) {
if ((key_1 = key_1.match(use_function))
//
&& (key_2 = key_2.match(use_function)))
return key_1[1] - key_2[1];
});
else
order.sort();
library_namespace.debug(order.join('<br />'), 2);
if (options) {
if (!options.no_set_order) {
this.order = order;
if (comparator === 'function' && !options.no_set_sort)
this.comparator = comparator;
}
if (options.get_list)
return order;
}
return this;
}
var get_file_extension, read_file, write_file, guess_encoding;
function setup_Files(function_body, function_name) {
if (!function_name)
function_name = library_namespace.get_function_name(function_body);
setup_Files.conversion[function_name] = function_body;
return function() {
setup_Files.setup();
return function_body.apply(this, arguments);
};
}
setup_Files.conversion = {};
setup_Files.setup = function() {
library_namespace.debug('Trying setup Files.', 1, 'setup_Files.setup');
library_namespace.is_included([ 'application.storage.file',
'application.OS.Windows.file', 'application.locale.encoding' ],
true);
get_file_extension = library_namespace.
//
application.storage.file.get_file_extension;
var file = library_namespace.application.OS.Windows.file;
read_file = file.read_file;
write_file = file.write_file;
guess_encoding = library_namespace.
//
application.locale.encoding.guess_encoding;
Object.assign(Files.prototype, setup_Files.conversion);
library_namespace.debug('Setup Files done.', 1, 'setup_Files.setup');
};
function detect_encoding(this_Files, path, force) {
var data = this_Files.data.path;
if (!data)
return;
if (force || !data.encoding)
// cache encoding
data.encoding = guess_encoding(path);
library_namespace.debug('(' + data.encoding + ') [' + path + ']');
return data.encoding;
}
function decide_target(path, options) {
var target = typeof options.target === 'function'
// default: overwrite / save to original file.
? options.target(path) : path;
if (typeof options.target_file === 'function') {
// get file name.
target = target.match(/^(.*?)([^\\\/]+)$/);
target = target[1] + options.target_file(target[2]);
}
if (target !== path) {
library_namespace.debug('check extension 一致性。', 2);
var has_error,
//
ext1 = get_file_extension(path),
//
ext2 = get_file_extension(target);
if (ext1 !== ext2) {
has_error = 1;
library_namespace.error('副檔名不同! [' + ext1 + ']→[' + ext2 + ']');
}
library_namespace.debug('check target_file 檔案存在與否。', 2);
if (FSO.FileExists(target)) {
library_namespace.debug('check target_file 檔案大小是否差異太大。', 2);
var ratio = FSO.GetFile(target).Size / FSO.GetFile(path).Size;
if (ratio > 8) {
has_error = 2;
library_namespace.error('目標檔案 [' + target_file
+ '] 已存在,且檔案大小差異太大!');
} else {
library_namespace.warn('目標檔案 [' + target_file + '] 已存在!'
+ (has_error ? '' : '將覆寫之。'));
}
}
if (has_error)
return;
}
library_namespace.debug('['
+ path
+ ']'
+ (path === target ? ': original = decided' : '<br />→ ['
+ target + ']'), 2);
return target;
}
/**
* @example <code>
// Files_encode('target encoding')
// if set save_to, then backup_to will be ignored.
options = {
from : 'encoding',
save_to : function(path) {
return 'save to';
},
backup_to : function(path) {
return 'save to';
}
}
</code>
*/
function Files_encode(target_encoding, options) {
if (!options)
options = Object.create(null);
var modify = typeof options.modify === 'function' && options.modify,
//
filter = is_regular_filter(options.filter) && options.filter,
//
handle_file = function(path) {
if (!match_filter(filter, path))
return;
library_namespace.debug('處理 [' + path + ']', 2, 'Files_encode');
var source_encoding = options.source_encoding
|| detect_encoding(this, path) || this.encoding,
//
source_text, converted_text,
//
target_file = decide_target(path, options);
if (!target_file)
return;
source_text = read_file(path, source_encoding);
converted_text = modify ? modify(text) : text;
if (typeof converted_text === 'string'
//
&& (source_text !== converted_text
//
|| source_enc && target_encoding
//
&& source_enc !== target_encoding)) {
// TODO: !!workaround!!
write_file(target_file, converted_text,
// TODO: truncate file
target_encoding);
} else if (target_file !== path) {
// copy file.
library_namespace.debug(path + ' → ' + target_file);
FSO.CopyFile(path, target_file);
}
};
if (target_encoding || modify)
this.list().forEach(handle_file, this);
else
library_namespace.warn(
//
'Files_encode: Target encoding does not specified.');
return this;
}
function Files_replace(RegExp_from, to) {
return this.encode(null, {
modify : function(text) {
return text.replace(RegExp_from, to);
}
});
}
function Files_merge(target, options) {
if (!target)
// TODO: auto detect.
throw new Error('Files_merge: No target specified!');
target = decide_target(target, options);
if (!target)
return;
if (!options)
options = Object.create(null);
else if (typeof options === 'string')
options = {
encoding : options
};
var encoding = options.encoding,
//
modify = typeof options.modify === 'function'
//
&& options.modify,
//
filter = is_regular_filter(options.filter) && options.filter,
//
text_Array = [],
//
add_text = function(item, path) {
item = options[item];
if (!item)
return;
if (typeof item === 'string')
text_Array.push(item);
else if (typeof item === 'function')
text_Array.push(item(path));
else if (Array.isArray(item))
Array.prototype.push.apply(
//
text_Array, item);
else
library_namespace.debug('Unknown text: [' + item + ']', 3);
},
//
handle_file = function(path) {
if (!match_filter(filter, path))
return;
library_namespace.debug('處理 [' + path + ']', 2, 'Files_merge');
var encoding = options.source_encoding
//
|| detect_encoding(this, path),
//
text = read_file(path, encoding);
library_namespace.debug('(' + encoding + ') [' + path + ']: '
+ text.length + ' characters.', 3);
if (modify) {
text = modify(text, path);
library_namespace.debug(
//
'→ ' + text.length + ' characters.', 3);
if (text.length === 0)
library_namespace.warn('[' + path + ']: 經 modify 後,無內容回傳!');
}
add_text('subhead', path);
text_Array.push(text);
// sub-footer
add_text('subfooter', path);
};
if (!encoding)
encoding = FSO.FileExists(target) && detect_encoding(this, target)
|| this.encoding;
if (library_namespace.is_debug(3))
library_namespace.debug('Merge ' + this.list().length
//
+ ' files to (' + encoding + ') [' + target + '].');
add_text('head', target);
this.list().forEach(handle_file, this);
add_text('footer', target);
text_Array = text_Array.join('');
if (typeof options.modify_all === 'function')
text_Array = options.modify_all(text_Array);
library_namespace.debug('Merge ' + this.order.length
+ ' files, writing ' + text_Array.length + ' characters to ('
+ encoding + ') [' + target + '].');
write_file(target, text_Array, encoding);
(this.data = {})[target] = {};
delete this.order;
return this;
}
/**
* batch operation
*
* @param {Function}callback
* callback(path, data)
* @param {Function}comparator
* @returns {@Files}
*/
function Files_forEach(callback, comparator) {
var data = this.data,
//
order = comparator ? this.sort(comparator, {
no_set_order : true,
get_list : true
}) : this.list();
order.forEach(function(path) {
library_namespace.debug('處理 [' + path + ']', 2, 'Files_forEach');
callback(path, data[path]);
});
return this;
}
/**
* get HTML contents.<br />
* get contents between "body" tag.
*
* @param {String}HTML
* HTML text
* @returns {String}contents
* @since 2014/7/19 23:9:41
*/
function HTML_contents(HTML, use_RegExp) {
if (use_RegExp)
return HTML.replace(/[\s\n]*<\/body>(.+|\n)*$/i, '')
// default 不用 RegExp因為 RegExp 太慢。
.replace(/^(.+|\n)*<body>[\s\n]*/i, '');
var foot = HTML.lastIndexOf('</body>'),
//
head = HTML.indexOf('<body');
// 去尾。
if (false && foot === NOT_FOUND)
foot = HTML.lastIndexOf('</BODY>');
if (foot === NOT_FOUND) {
foot = HTML.lastIndexOf('</div>');
if (foot !== NOT_FOUND && !/^[\s\n]*$/.test(HTML.slice(foot)))
foot = NOT_FOUND;
}
// 去頭。
if (false && head === NOT_FOUND)
head = HTML.indexOf('<BODY');
if (head === NOT_FOUND) {
head = HTML.indexOf('<div');
if (head !== NOT_FOUND && !/^[\s\n]*$/.test(HTML.slice(0, head)))
head = NOT_FOUND;
}
head = HTML.indexOf('>', head) + 1;
// 切割。
if (foot === NOT_FOUND) {
if (head !== NOT_FOUND)
HTML = HTML.slice(head);
} else
HTML = HTML.slice(head, foot);
return HTML;
}
// ---------------------------------------------------------------------//
// public interface of Files.
Object.assign(Files.prototype, {
add : Files_add,
list : Files_list,
sort : Files_sort,
// convert encoding
encode : setup_Files(Files_encode, 'encode'),
merge : setup_Files(Files_merge, 'merge'),
replace : setup_Files(Files_replace, 'replace'),
forEach : Files_forEach,
encoding : 'UTF-8'
});
// ---------------------------------------------------------------------//
// export.
return Object.assign(Files, {
HTML_contents : HTML_contents,
file_system_structure : file_system_structure
});
}