mirror of
https://scm.univ-tours.fr/22107988t/rappaurio-sae501_502.git
synced 2025-08-29 23:45:59 +02:00
1701 lines
46 KiB
JavaScript
1701 lines
46 KiB
JavaScript
/**
|
||
* @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
|
||
});
|
||
|
||
}
|