<commit_before>/**
 * cloud service
 */
const path = require('path')
const fs = require('fs')
const fsUtil = require('../bin/util/fsUtil')
const db = require('../bin/database/db')
const cryptUtil = require('../bin/util/cryptUtil')
const authCloudUtil = require('../bin/util/authCloudUtil')
const commonService = require('./commonSevice')
const config = require('../bin/util/configUtil')
const validaUtil = require('../bin/util/validaUtil')

// 根目录
const baseDirector = config.Config.getInstance().baseFolder
if (!fs.existsSync(baseDirector)) {
    fsUtil.mkdirSync(baseDirector)
}

let tempDownloadDir = path.resolve(process.cwd(), 'download')
// 创建tempDownloadDir
function createTempDownloadDir() {
    if (!fs.existsSync(tempDownloadDir)) {
        fsUtil.createfolder(tempDownloadDir)
    }
}
createTempDownloadDir()

// 获取授权fs
function authFolder(opts, userId, done) {
    let result = {
        status: false
    }
    let folderTree = {
        name: path.basename(baseDirector),
        file: [],
        folder: []
    }
    commonService.validAdmin(userId).then(isAdmin => {
        if (isAdmin) {
            let fullPath = baseDirector
            fsUtil.walk(fullPath, folderTree, (err, fsResult) => {
                result.status = true
                result.data = folderTree
                done(result)
            })
        } else {
            let authTable = db.table('auth')
            return authTable.find({
                userId
            })
        }
    }).then(data => {
        if (data && data.length >= 0) {
            data.forEach(item => {
                let authFolder = item.folder
                let auth = {
                    subinherit: item.subinherit,
                    subinherit: item.foldercreate,
                    folderdelete: item.folderdelete,
                    folderupload: item.folderupload,
                    folderdownload: item.folderdownload,
                    folderename: item.folderename,
                    filedownload: item.filedownload,
                    filedelete: item.filedelete,
                    filerename: item.filerename
                }
                let authfolderData = getFolderInfo(folderTree, baseDirector, authFolder, auth)
                if (!authfolderData) {
                    // 授权已失效，删除数据库中此授权
                    result.data = folderTree
                    done(result)
                } else {
                    let fullPath = baseDirector + authFolder
                    fsUtil.walk(fullPath, authfolderData, (err, fsResult) => {
                        result.status = true
                        result.data = folderTree
                        done(result)
                    })
                }
            })
        }
    }).catch(error => {
        result.message = error && typeof error === 'string' ? error : '系统错误！'
        done(result)
    })

}

function getFolderInfo(folderTree, root, authFolder, auth) {
    let authFolderArr = authFolder.split('/').filter(item => {
        return !!item
    });
    // 无效授权：授权目录已被删除。
    let invalid = false
    let curOperateFolder = folderTree
    let curRealPath = root
    for (let i = 0; i < authFolderArr.length; i++) {
        let authFolderItem = authFolderArr[i]
        curRealPath = path.resolve(curRealPath, authFolderItem);
        if (!fs.existsSync(curRealPath)) {
            invalid = true;
            break
        }
        let child = {
            name: authFolderItem,
            file: [],
            folder: []
        }
        if (i === authFolderArr.length - 1) {
            child.auth = auth
        }
        curOperateFolder.folder.push(child)
        curOperateFolder = child
    }
    // 填充文件、文件夹 curOperateFolder
    if (invalid) return false
    else return curOperateFolder
}

// 创建文件夹
function createFolder(userId, opts, done) {
    let result = {
        status: false
    }
    let {
        baseFolder,
        newFolder
    } = opts
    // 参数验证
    if (!validaUtil.vString(baseFolder) || !validaUtil.vString(newFolder)) {
        result.message = '缺少参数！'
        done(result)
    } else if (!validaUtil.vFolderName(newFolder)) {
        result.message = '文件夹名不能包含下列任何字符:\/:*?"<>|'
        done(result)
    }
    // 获取用户权限
    commonService.getAuthInfo(userId, baseFolder).then(auth => {
        // 权限判定
        if (auth.admin || auth.createfolder) {
            let baseFolderPath = path.resolve(baseDirector, baseFolder)
            if (!fs.existsSync(baseFolderPath)) {
                result.message = '当前操作目录不存在，请刷新后重试！'
                done(result)
                return
            }
            let name = newFolder
            let folderPath = path.resolve(baseFolderPath, newFolder)
            let index = 1
            while (fs.existsSync(folderPath)) {
                name = `${newFolder}(${index})`
                folderPath = path.resolve(baseFolderPath, name)
                index++
            }
            fs.mkdirSync(folderPath);
            result.status = true
            result.newName = name
            // if (!fs.existsSync(folderPath)) {
            //     fs.mkdirSync(folderPath);
            //     // 实体目录创建文件夹
            //     result.status = true
            // } else {
            //     result.message = '此文件夹已存在！'
            // }
            done(result)
        } else {
            result.message = '您没有此目录下的创建目录权限！'
            done(result)
        }
    })
}
// 删除文件夹
function deleteFolder(userId, opts, done) {
    let result = {
        status: false
    }
    let {
        baseFolder,
        delFolder
    } = opts
    // 参数验证 TODO  yangchanlgu 去掉!validaUtil.vString(baseFolder) 不需要判断根目录是否为空
    if (!validaUtil.vString(delFolder)) {
        result.message = '缺少参数！'
        done(result)
    } else {
        // 获取用户权限
        commonService.getAuthInfo(userId, baseFolder).then(auth => {
            // 权限判定
            if (auth.admin || auth.deletefolder) {
                let folderPath = path.resolve(baseDirector, baseFolder, delFolder)
                if (fs.existsSync(folderPath)) {
                    fsUtil.deleteFolder(folderPath)
                }
                result.status = true
                done(result)
            } else {
                result.message = '您没有此目录下的删除目录权限！'
                done(result)
            }
        })
    }
}
// 重命名文件夹
function renameFolder(userId, opts, done) {
    let result = {
        status: false
    }
    let {
        baseFolder,
        oldName,
        newName
    } = opts
    // 参数验证
    if (!validaUtil.vString(oldName) || !validaUtil.vString(newName)) {
        result.message = '缺少参数！'
        done(result)
    } else {
        // 权限判定
        commonService.getAuthInfo(userId, baseFolder).then(auth => {
            if (auth.admin || auth.renamefolder) {
                let baseFolderPath = path.resolve(baseDirector, baseFolder)
                let oldPath = path.resolve(baseFolderPath, oldName)
                let newPath = path.resolve(baseFolderPath, newName)
                if (fs.existsSync(oldPath)) {
                    fs.renameSync(oldPath, newPath)
                    result.status = true
                } else {
                    result.message = '此文件夹不存在，请刷新后重试！'
                }

                done(result)
            } else {
                result.message = '您没有此文件夹的重命名权限！'
                done(result)
            }
        })
    }
}
// 下载文件夹
function downloadFolder(userId, opts, done) {
    let result = {
        status: false
    }
    let {
        folder,
    } = opts
    // 参数验证
    if (!validaUtil.vString(folder)) {
        result.message = '缺少参数！'
        done(result)
    }
    // 获取用户权限
    let baseFolder = path.dirname(folder)
    commonService.getAuthInfo(userId, baseFolder).then(auth => {
        // 权限判定
        if (auth.admin || auth.downloadfolder) {
            let folderPath = path.resolve(path.dirname(baseDirector), folder)
            let tempName = cryptUtil.guid()
            let output = path.resolve(tempDownloadDir, tempName + '.zip')
            // 压缩zip
            return fsUtil.compression(folderPath, output)
        } else {
            result.message = '您没有下载此目录的权限！'
            done(result)
        }
    }).then(compressRes => {
        if (compressRes.status) {
            let outputPath = compressRes.data.output
            result.status = true
            result.data = {
                filePath: fs.existsSync(outputPath) ? outputPath : '',
                fileRealName: path.basename(folder)
            }
            done(result)
        } else {
            result.message = compressRes.message || compressRes || '系统错误，请刷新后重试！'
            done(result)
        }
    })
}
// 文件夹属性
function attrFolder(opts, done) {
    /**
     *  文件夹占用空间、文件夹个数、文件个数
     */
    let result = {
        status: false
    }
    let {
        folderPath
    } = opts
    // 参数验证
    if (!validaUtil.vString(folderPath)) {
        result.message = '缺少参数！'
        done(result)
    }
    let realPath = path.resolve(baseDirector, folderPath)
    if (fs.existsSync(realPath)) {
        let stat = fs.statSync(realPath)
        fsUtil.getFolderProperty(realPath, data => {
            result.status = true
            result.data = Object.assign({}, data, {
                filePath: folderPath,
                fileSize: data.size,
                birthtime: stat.birthtime
            })

            done(result)
        })
    } else {
        result.message = '此文件不存在！'
        done(result)
    }
}
/** --------------- 文件接口 ---------------- */
// 上传文件
function uploadFile(userId, opts, done) {

}
// 下载文件
function downloadFile(userId, opts, done) {
    let result = {
        status: false
    }
    let filePath = opts.filePath
    if (!validaUtil.vString(filePath)) {
        result.message = '缺少参数！'
        done(result)
    }
    let baseFolder = path.dirname(filePath)
    // 权限判定
    commonService.getAuthInfo(userId, baseFolder).then(auth => {
        if (auth.admin || auth.downloadfile) {
            let rootPath = path.resolve(baseDirector, filePath)
            result.status = true
            result.data = fs.existsSync(rootPath) ? rootPath : ''
        } else {
            result.message = '您没有下载此文件的权限！'
        }
        done(result)
    })
}
// 文件重命名
function renameFile(userId, opts, done) {
    let result = {
        status: false
    }
    let {
        baseFolder,
        oldName,
        newName
    } = opts
    // 参数验证
    if (!validaUtil.vString(oldName) || !validaUtil.vString(newName)) {
        result.message = '缺少参数！'
        done(result)
    } else {
        // 权限判定
        commonService.getAuthInfo(userId, baseFolder).then(auth => {
            if (auth.admin || auth.renamefile) {
                let baseFolderPath = path.resolve(baseDirector, baseFolder)
                let oldPath = path.resolve(baseFolderPath, oldName)
                let newPath = path.resolve(baseFolderPath, newName)
                if (fs.existsSync(oldPath)) {
                    fs.renameSync(oldPath, newPath)
                    result.status = true
                } else {
                    result.message = '此文件不存在，请刷新后重试！'
                }
                done(result)
            } else {
                result.message = '您没有此文件的重命名权限！'
                done(result)
            }
        })
    }
}
// 删除文件
function deleteFile(userId, opts, done) {
    let result = {
        status: false
    }
    let {
        baseFolder,
        delFileNames
    } = opts
    // 参数验证
    if (!validaUtil.vString(baseFolder)) {
        result.message = '缺少参数！'
        done(result)
    }
    delFileNames = delFileNames.split('*')
    // 获取用户权限
    commonService.getAuthInfo(userId, baseFolder).then(auth => {
        // 权限判定
        if (auth.admin || auth.deletefolder) {
            delFileNames.forEach((delFileName) => {
                let filePath = path.resolve(baseDirector, baseFolder, delFileName)
                if (fs.existsSync(filePath)) {
                    fs.unlinkSync(filePath);
                }
            })
            result.status = true
            done(result)
        } else {
            result.message = '您没有删除此文件的权限！'
            done(result)
        }
    })
}
// 文件属性
function attrFile(opts, done) {
    let result = {
        status: false
    }
    let {
        filePath
    } = opts
    // 参数验证
    if (!validaUtil.vString(filePath)) {
        result.message = '缺少参数！'
        done(result)
    }
    let realPath = path.resolve(baseDirector, filePath)
    if (fs.existsSync(realPath)) {
        let stat = fs.statSync(realPath)
        result.status = true
        result.data = {
            fileName: path.basename(realPath),
            filePath: filePath,
            fileSize: stat.size,
            cTime: stat.ctime,
            mTime: stat.mtime,
            aTime: stat.atime
        }
    } else {
        result.message = '此文件不存在！'
    }
    done(result)
}
module.exports = {
    authFolder,
    createFolder,
    deleteFolder,
    renameFolder,
    downloadFolder,
    attrFolder,
    uploadFile,
    downloadFile,
    renameFile,
    deleteFile,
    attrFile
}<commit_msg>添加创建时间和访问时间等属性获取<commit_after>/**
 * cloud service
 */
const path = require('path')
const fs = require('fs')
const fsUtil = require('../bin/util/fsUtil')
const db = require('../bin/database/db')
const cryptUtil = require('../bin/util/cryptUtil')
const authCloudUtil = require('../bin/util/authCloudUtil')
const commonService = require('./commonSevice')
const config = require('../bin/util/configUtil')
const validaUtil = require('../bin/util/validaUtil')

// 根目录
const baseDirector = config.Config.getInstance().baseFolder
if (!fs.existsSync(baseDirector)) {
    fsUtil.mkdirSync(baseDirector)
}

let tempDownloadDir = path.resolve(process.cwd(), 'download')
// 创建tempDownloadDir
function createTempDownloadDir() {
    if (!fs.existsSync(tempDownloadDir)) {
        fsUtil.createfolder(tempDownloadDir)
    }
}
createTempDownloadDir()

// 获取授权fs
function authFolder(opts, userId, done) {
    let result = {
        status: false
    }
    let folderTree = {
        name: path.basename(baseDirector),
        file: [],
        folder: []
    }
    commonService.validAdmin(userId).then(isAdmin => {
        if (isAdmin) {
            let fullPath = baseDirector
            fsUtil.walk(fullPath, folderTree, (err, fsResult) => {
                result.status = true
                result.data = folderTree
                done(result)
            })
        } else {
            let authTable = db.table('auth')
            return authTable.find({
                userId
            })
        }
    }).then(data => {
        if (data && data.length >= 0) {
            data.forEach(item => {
                let authFolder = item.folder
                let auth = {
                    subinherit: item.subinherit,
                    subinherit: item.foldercreate,
                    folderdelete: item.folderdelete,
                    folderupload: item.folderupload,
                    folderdownload: item.folderdownload,
                    folderename: item.folderename,
                    filedownload: item.filedownload,
                    filedelete: item.filedelete,
                    filerename: item.filerename
                }
                let authfolderData = getFolderInfo(folderTree, baseDirector, authFolder, auth)
                if (!authfolderData) {
                    // 授权已失效，删除数据库中此授权
                    result.data = folderTree
                    done(result)
                } else {
                    let fullPath = baseDirector + authFolder
                    fsUtil.walk(fullPath, authfolderData, (err, fsResult) => {
                        result.status = true
                        result.data = folderTree
                        done(result)
                    })
                }
            })
        }
    }).catch(error => {
        result.message = error && typeof error === 'string' ? error : '系统错误！'
        done(result)
    })

}

function getFolderInfo(folderTree, root, authFolder, auth) {
    let authFolderArr = authFolder.split('/').filter(item => {
        return !!item
    });
    // 无效授权：授权目录已被删除。
    let invalid = false
    let curOperateFolder = folderTree
    let curRealPath = root
    for (let i = 0; i < authFolderArr.length; i++) {
        let authFolderItem = authFolderArr[i]
        curRealPath = path.resolve(curRealPath, authFolderItem);
        if (!fs.existsSync(curRealPath)) {
            invalid = true;
            break
        }
        let child = {
            name: authFolderItem,
            file: [],
            folder: []
        }
        if (i === authFolderArr.length - 1) {
            child.auth = auth
        }
        curOperateFolder.folder.push(child)
        curOperateFolder = child
    }
    // 填充文件、文件夹 curOperateFolder
    if (invalid) return false
    else return curOperateFolder
}

// 创建文件夹
function createFolder(userId, opts, done) {
    let result = {
        status: false
    }
    let {
        baseFolder,
        newFolder
    } = opts
    // 参数验证
    if (!validaUtil.vString(baseFolder) || !validaUtil.vString(newFolder)) {
        result.message = '缺少参数！'
        done(result)
    } else if (!validaUtil.vFolderName(newFolder)) {
        result.message = '文件夹名不能包含下列任何字符:\/:*?"<>|'
        done(result)
    }
    // 获取用户权限
    commonService.getAuthInfo(userId, baseFolder).then(auth => {
        // 权限判定
        if (auth.admin || auth.createfolder) {
            let baseFolderPath = path.resolve(baseDirector, baseFolder)
            if (!fs.existsSync(baseFolderPath)) {
                result.message = '当前操作目录不存在，请刷新后重试！'
                done(result)
                return
            }
            let name = newFolder
            let folderPath = path.resolve(baseFolderPath, newFolder)
            let index = 1
            while (fs.existsSync(folderPath)) {
                name = `${newFolder}(${index})`
                folderPath = path.resolve(baseFolderPath, name)
                index++
            }
            fs.mkdirSync(folderPath);
            result.status = true
            result.newName = name
            // if (!fs.existsSync(folderPath)) {
            //     fs.mkdirSync(folderPath);
            //     // 实体目录创建文件夹
            //     result.status = true
            // } else {
            //     result.message = '此文件夹已存在！'
            // }
            done(result)
        } else {
            result.message = '您没有此目录下的创建目录权限！'
            done(result)
        }
    })
}
// 删除文件夹
function deleteFolder(userId, opts, done) {
    let result = {
        status: false
    }
    let {
        baseFolder,
        delFolder
    } = opts
    // 参数验证 TODO  yangchanlgu 去掉!validaUtil.vString(baseFolder) 不需要判断根目录是否为空
    if (!validaUtil.vString(delFolder)) {
        result.message = '缺少参数！'
        done(result)
    } else {
        // 获取用户权限
        commonService.getAuthInfo(userId, baseFolder).then(auth => {
            // 权限判定
            if (auth.admin || auth.deletefolder) {
                let folderPath = path.resolve(baseDirector, baseFolder, delFolder)
                if (fs.existsSync(folderPath)) {
                    fsUtil.deleteFolder(folderPath)
                }
                result.status = true
                done(result)
            } else {
                result.message = '您没有此目录下的删除目录权限！'
                done(result)
            }
        })
    }
}
// 重命名文件夹
function renameFolder(userId, opts, done) {
    let result = {
        status: false
    }
    let {
        baseFolder,
        oldName,
        newName
    } = opts
    // 参数验证
    if (!validaUtil.vString(oldName) || !validaUtil.vString(newName)) {
        result.message = '缺少参数！'
        done(result)
    } else {
        // 权限判定
        commonService.getAuthInfo(userId, baseFolder).then(auth => {
            if (auth.admin || auth.renamefolder) {
                let baseFolderPath = path.resolve(baseDirector, baseFolder)
                let oldPath = path.resolve(baseFolderPath, oldName)
                let newPath = path.resolve(baseFolderPath, newName)
                if (fs.existsSync(oldPath)) {
                    fs.renameSync(oldPath, newPath)
                    result.status = true
                } else {
                    result.message = '此文件夹不存在，请刷新后重试！'
                }

                done(result)
            } else {
                result.message = '您没有此文件夹的重命名权限！'
                done(result)
            }
        })
    }
}
// 下载文件夹
function downloadFolder(userId, opts, done) {
    let result = {
        status: false
    }
    let {
        folder,
    } = opts
    // 参数验证
    if (!validaUtil.vString(folder)) {
        result.message = '缺少参数！'
        done(result)
    }
    // 获取用户权限
    let baseFolder = path.dirname(folder)
    commonService.getAuthInfo(userId, baseFolder).then(auth => {
        // 权限判定
        if (auth.admin || auth.downloadfolder) {
            let folderPath = path.resolve(path.dirname(baseDirector), folder)
            let tempName = cryptUtil.guid()
            let output = path.resolve(tempDownloadDir, tempName + '.zip')
            // 压缩zip
            return fsUtil.compression(folderPath, output)
        } else {
            result.message = '您没有下载此目录的权限！'
            done(result)
        }
    }).then(compressRes => {
        if (compressRes.status) {
            let outputPath = compressRes.data.output
            result.status = true
            result.data = {
                filePath: fs.existsSync(outputPath) ? outputPath : '',
                fileRealName: path.basename(folder)
            }
            done(result)
        } else {
            result.message = compressRes.message || compressRes || '系统错误，请刷新后重试！'
            done(result)
        }
    })
}
/**
 * 文件夹属性
 * atime "访问时间" - 文件数据最近被访问的时间。 会被 mknod(2)、 utimes(2) 和 read(2) 系统调用改变。
 * mtime "修改时间" - 文件数据最近被修改的时间。 会被 mknod(2)、 utimes(2) 和 write(2) 系统调用改变。
 * ctime "变化时间" - 文件状态最近更改的时间（修改索引节点数据） 会被 chmod(2)、 chown(2)、 link(2)、 mknod(2)、 rename(2)、 unlink(2)、 utimes(2)、 read(2) 和 write(2) 系统调用改变。
 * birthtime "创建时间" - 文件创建的时间。 当文件被创建时设定一次。 在创建时间不可用的文件系统中，该字段可能被替代为 ctime 或 1970-01-01T00:00Z（如 Unix 的纪元时间戳 0）。 注意，该值在此情况下可能会大于 atime 或 mtime。 在 Darwin 和其它的 FreeBSD 衍生系统中，如果 atime 被使用 utimes(2) 系统调用显式地设置为一个比当前 birthtime 更早的值，也会有这种情况。
 * */
function attrFolder(opts, done) {
    /**
     *  文件夹占用空间、文件夹个数、文件个数
     */
    let result = {
        status: false
    }
    let {
        folderPath
    } = opts
    // 参数验证
    if (!validaUtil.vString(folderPath)) {
        result.message = '缺少参数！'
        done(result)
    }
    let realPath = path.resolve(baseDirector, folderPath)
    if (fs.existsSync(realPath)) {
        let stat = fs.statSync(realPath)
        fsUtil.getFolderProperty(realPath, data => {
            result.status = true
            result.data = Object.assign({}, data, {
                filePath: folderPath,
                fileSize: data.size,
                birthtime: stat.birthtime,
                cTime: stat.ctime,
                mTime: stat.mtime,
                aTime: stat.atime
            })

            done(result)
        })
    } else {
        result.message = '此文件不存在！'
        done(result)
    }
}
/** --------------- 文件接口 ---------------- */
// 上传文件
function uploadFile(userId, opts, done) {

}
// 下载文件
function downloadFile(userId, opts, done) {
    let result = {
        status: false
    }
    let filePath = opts.filePath
    if (!validaUtil.vString(filePath)) {
        result.message = '缺少参数！'
        done(result)
    }
    let baseFolder = path.dirname(filePath)
    // 权限判定
    commonService.getAuthInfo(userId, baseFolder).then(auth => {
        if (auth.admin || auth.downloadfile) {
            let rootPath = path.resolve(baseDirector, filePath)
            result.status = true
            result.data = fs.existsSync(rootPath) ? rootPath : ''
        } else {
            result.message = '您没有下载此文件的权限！'
        }
        done(result)
    })
}
// 文件重命名
function renameFile(userId, opts, done) {
    let result = {
        status: false
    }
    let {
        baseFolder,
        oldName,
        newName
    } = opts
    // 参数验证
    if (!validaUtil.vString(oldName) || !validaUtil.vString(newName)) {
        result.message = '缺少参数！'
        done(result)
    } else {
        // 权限判定
        commonService.getAuthInfo(userId, baseFolder).then(auth => {
            if (auth.admin || auth.renamefile) {
                let baseFolderPath = path.resolve(baseDirector, baseFolder)
                let oldPath = path.resolve(baseFolderPath, oldName)
                let newPath = path.resolve(baseFolderPath, newName)
                if (fs.existsSync(oldPath)) {
                    fs.renameSync(oldPath, newPath)
                    result.status = true
                } else {
                    result.message = '此文件不存在，请刷新后重试！'
                }
                done(result)
            } else {
                result.message = '您没有此文件的重命名权限！'
                done(result)
            }
        })
    }
}
// 删除文件
function deleteFile(userId, opts, done) {
    let result = {
        status: false
    }
    let {
        baseFolder,
        delFileNames
    } = opts
    // 参数验证
    if (!validaUtil.vString(baseFolder)) {
        result.message = '缺少参数！'
        done(result)
    }
    delFileNames = delFileNames.split('*')
    // 获取用户权限
    commonService.getAuthInfo(userId, baseFolder).then(auth => {
        // 权限判定
        if (auth.admin || auth.deletefolder) {
            delFileNames.forEach((delFileName) => {
                let filePath = path.resolve(baseDirector, baseFolder, delFileName)
                if (fs.existsSync(filePath)) {
                    fs.unlinkSync(filePath);
                }
            })
            result.status = true
            done(result)
        } else {
            result.message = '您没有删除此文件的权限！'
            done(result)
        }
    })
}
// 文件属性
function attrFile(opts, done) {
    let result = {
        status: false
    }
    let {
        filePath
    } = opts
    // 参数验证
    if (!validaUtil.vString(filePath)) {
        result.message = '缺少参数！'
        done(result)
    }
    let realPath = path.resolve(baseDirector, filePath)
    if (fs.existsSync(realPath)) {
        let stat = fs.statSync(realPath)
        result.status = true
        result.data = {
            fileName: path.basename(realPath),
            filePath: filePath,
            fileSize: stat.size,
            cTime: stat.ctime,
            mTime: stat.mtime,
            aTime: stat.atime,
            birthtime: stat.birthtime
        }
    } else {
        result.message = '此文件不存在！'
    }
    done(result)
}
module.exports = {
    authFolder,
    createFolder,
    deleteFolder,
    renameFolder,
    downloadFolder,
    attrFolder,
    uploadFile,
    downloadFile,
    renameFile,
    deleteFile,
    attrFile
}<|endoftext|>


<commit_before>import WRISerializer from 'wri-json-api-serializer';

// utils
import { WRIAPI } from 'utils/axios';
import { logger } from 'utils/logs';

/**
 * Retrieve all collections of the user
 * @param {*} token User's token
 * @param {*} params Request optional parameters
 */
export const fetchAllCollections = (
  token,
  params = {
    env: process.env.API_ENV,
    application: process.env.APPLICATIONS
  }
) => {
  logger.info('Fetch all collections');
  return WRIAPI.get('collection', {
    headers: {
      Authorization: token,
      'Upgrade-Insecure-Requests': 1
    },
    params
  })
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(`Error fetching all collections: ${status}: ${statusText}`);
      throw new Error(`Error fetching all collections: ${status}: ${statusText}`);
    });
};
/**
 * Retrieve data of a specific collection
 * @param {*} token User's token
 * @param {*} collectionId Id of the collection we are asking for.
 * @param {*} params Request parameters
 */
export const fetchCollection = (
  token,
  collectionId,
  params = {
    env: process.env.API_ENV,
    application: process.env.APPLICATIONS
  }
) => {
  logger.info(`Fetch collection ${collectionId}`);
  return WRIAPI.get(`collection/${collectionId}`, {
    headers: {
      Authorization: token,
      'Upgrade-Insecure-Requests': 1
    },
    params
  })
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(`Error fetching collection ${collectionId}: ${status}: ${statusText}`);
      throw new Error(`Error fetching collection ${collectionId}: ${status}: ${statusText}`);
    });
};

/**
 * Creates a new collection attached to the current user
 * @param {*} token User's token
 * @param {*} data collection data
 */
export const createCollection = (token, data = {}) => {
  logger.info('Create collection');
  return WRIAPI.post(`${process.env.WRI_API_URL}/collection`, data, {
    headers: {
      'Content-Type': 'application/json',
      Authorization: token
    }
  })
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(`Error creating collection: ${status}: ${statusText}`);
      // we shouldn't assume 400 is duplicated collection,
      // but there's no another way to find it out at this moment
      if (status === 400) {
        throw new Error(`Collection duplicated. The collection "${data.name}" already exists.`);
      } else {
        throw new Error(`Error creating collection: ${status}: ${statusText}`);
      }
    });
};

/**
 * Deletes an existing collection attached to the current user
 * @param {*} token User's token
 * @param {*} collectionId Id of the collection to be removed
 */
export const deleteCollection = (token, collectionId) => {
  logger.info(`Delete collection ${collectionId}`);
  return WRIAPI.delete(`collection/${collectionId}`, { headers: { Authorization: token } })
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(`Error deleting collection ${collectionId}: ${status}: ${statusText}`);
      throw new Error(`Error deleting collection ${collectionId}: ${status}: ${statusText}`);
    });
};

/**
 * Update an existing collection attached to the current user
 * @param {*} token User's token
 * @param {*} collectionId Id of the collection to be edited
 * @param {*} data Data to be updated
 */
export const updateCollection = (token, collectionId, data) => {
  logger.info(`Update collection ${collectionId}`);
  return WRIAPI.patch(`collection/${collectionId}`, data, {
    headers: {
      'content-type': 'application/json',
      Authorization: token
    }
  })
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(`Error updating collection ${collectionId}: ${status}: ${statusText}`);
      throw new Error(`Error updating collection ${collectionId}: ${status}: ${statusText}`);
    });
};

/* Resources management  */

/**
 *
 * @param {*} token User's token
 * @param {*} collectionId Id of the collection to be edited
 * @param {*} resource Resource to be addded to the collection
 */
export const addResourceToCollection = (token, collectionId, resource = []) => {
  logger.info(`Add resource to collection ${collectionId}`);
  return WRIAPI.post(
    `collection/${collectionId}/resource`,
    { ...resource },
    {
      headers: {
        'Content-Type': 'application/json',
        Authorization: token
      }
    }
  )
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(`Error adding resource to collection ${collectionId}: ${status}: ${statusText}`);
      throw new Error(
        `Error adding resource to collection ${collectionId}: ${status}: ${statusText}`
      );
    });
};

/**
 *
 * @param {*} token User's token
 * @param {*} collectionId Id of the collection to be edited
 * @param {*} resource Resource to be removed from the collection
 */
export const removeResourceFromCollection = (token, collectionId, resource = []) => {
  logger.info(`Remove resource from collection ${collectionId}`);
  const { type, id } = resource;
  return WRIAPI.delete(`collection/${collectionId}/resource/${type}/${id}`, { headers: { Authorization: token } })
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(
        `Error removing resource from collection ${collectionId}: ${status}: ${statusText}`
      );
      throw new Error(
        `Error removing resource from collection ${collectionId}: ${status}: ${statusText}`
      );
    });
};

export default {
  fetchAllCollections,
  fetchCollection,
  createCollection,
  deleteCollection,
  updateCollection,
  addResourceToCollection,
  removeResourceFromCollection
};
<commit_msg>couple of fixes for collections services<commit_after>import WRISerializer from 'wri-json-api-serializer';

// utils
import { WRIAPI } from 'utils/axios';
import { logger } from 'utils/logs';

/**
 * Retrieve all collections of the user
 * @param {*} token User's token
 * @param {*} params Request optional parameters
 */
export const fetchAllCollections = (
  token,
  params = {
    env: process.env.API_ENV,
    application: process.env.APPLICATIONS
  }
) => {
  logger.info('Fetch all collections');
  return WRIAPI.get('collection', {
    headers: {
      Authorization: token,
      'Upgrade-Insecure-Requests': 1
    },
    params
  })
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(`Error fetching all collections: ${status}: ${statusText}`);
      throw new Error(`Error fetching all collections: ${status}: ${statusText}`);
    });
};
/**
 * Retrieve data of a specific collection
 * @param {*} token User's token
 * @param {*} collectionId Id of the collection we are asking for.
 * @param {*} params Request parameters
 */
export const fetchCollection = (
  token,
  collectionId,
  params = {
    env: process.env.API_ENV,
    application: process.env.APPLICATIONS
  }
) => {
  logger.info(`Fetch collection ${collectionId}`);
  return WRIAPI.get(`collection/${collectionId}`, {
    headers: {
      Authorization: token,
      'Upgrade-Insecure-Requests': 1
    },
    params
  })
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(`Error fetching collection ${collectionId}: ${status}: ${statusText}`);
      throw new Error(`Error fetching collection ${collectionId}: ${status}: ${statusText}`);
    });
};

/**
 * Creates a new collection attached to the current user
 * @param {*} token User's token
 * @param {*} data collection data
 */
export const createCollection = (token, data = {}) => {
  logger.info('Create collection');
  return WRIAPI.post(`${process.env.WRI_API_URL}/collection`, data, {
    headers: {
      'Content-Type': 'application/json',
      Authorization: token
    }
  })
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(`Error creating collection: ${status}: ${statusText}`);
      // we shouldn't assume 400 is duplicated collection,
      // but there's no another way to find it out at this moment
      if (status === 400) {
        throw new Error(`Collection duplicated. The collection "${data.name}" already exists.`);
      } else {
        throw new Error(`Error creating collection: ${status}: ${statusText}`);
      }
    });
};

/**
 * Deletes an existing collection attached to the current user
 * @param {*} token User's token
 * @param {*} collectionId Id of the collection to be removed
 */
export const deleteCollection = (token, collectionId) => {
  logger.info(`Delete collection ${collectionId}`);
  return WRIAPI.delete(`collection/${collectionId}`, { headers: { Authorization: token } })
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(`Error deleting collection ${collectionId}: ${status}: ${statusText}`);
      throw new Error(`Error deleting collection ${collectionId}: ${status}: ${statusText}`);
    });
};

/**
 * Update an existing collection attached to the current user
 * @param {*} token User's token
 * @param {*} collectionId Id of the collection to be edited
 * @param {*} data Data to be updated
 */
export const updateCollection = (token, collectionId, data) => {
  logger.info(`Update collection ${collectionId}`);
  return WRIAPI.patch(`collection/${collectionId}`, data, {
    headers: {
      'content-type': 'application/json',
      Authorization: token
    }
  })
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(`Error updating collection ${collectionId}: ${status}: ${statusText}`);
      throw new Error(`Error updating collection ${collectionId}: ${status}: ${statusText}`);
    });
};

/* Resources management  */

/**
 *
 * @param {*} token User's token
 * @param {*} collectionId Id of the collection to be edited
 * @param {*} resource Resource to be addded to the collection
 */
export const addResourceToCollection = (token, collectionId, resource = {}) => {
  logger.info(`Add resource to collection ${collectionId}`);
  return WRIAPI.post(
    `collection/${collectionId}/resource`,
    { ...resource },
    {
      headers: {
        'Content-Type': 'application/json',
        Authorization: token
      }
    }
  )
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(`Error adding resource to collection ${collectionId}: ${status}: ${statusText}`);
      throw new Error(
        `Error adding resource to collection ${collectionId}: ${status}: ${statusText}`
      );
    });
};

/**
 *
 * @param {*} token User's token
 * @param {*} collectionId Id of the collection to be edited
 * @param {*} resource Resource to be removed from the collection
 */
export const removeResourceFromCollection = (token, collectionId, resource = {}) => {
  logger.info(`Remove resource from collection ${collectionId}`);
  const { type, id } = resource;
  return WRIAPI.delete(`collection/${collectionId}/resource/${type}/${id}`, { headers: { Authorization: token } })
    .then(response => WRISerializer(response.data))
    .catch(({ response }) => {
      const { status, statusText } = response;
      logger.error(
        `Error removing resource from collection ${collectionId}: ${status}: ${statusText}`
      );
      throw new Error(
        `Error removing resource from collection ${collectionId}: ${status}: ${statusText}`
      );
    });
};

export default {
  fetchAllCollections,
  fetchCollection,
  createCollection,
  deleteCollection,
  updateCollection,
  addResourceToCollection,
  removeResourceFromCollection
};
<|endoftext|>


<commit_before>/**
 * The toucher gremlin touches anywhere on the visible area of the document.
 *
 * The toucher gremlin triggers touch events (touchstart, touchmove, touchcancel
 * and touchend), by doing gestures on random targets displayed on the viewport.
 *
 * By default, the touch gremlin activity is showed by a blue circle.
 *
 *   var toucherGremlin = gremlins.species.toucher();
 *   horde.gremlin(toucherGremlin);
 *
 * The toucher gremlin can be customized as follows:
 *
 *   toucherGremlin.touchTypes(['tap', 'gesture']); // the mouse event types to trigger
 *   toucherGremlin.positionSelector(function() { // find a random pair of coordinates to touch });
 *   toucherGremlin.showAction(function(x, y) { // show the gremlin activity on screen });
 *   toucherGremlin.canTouch(function(element) { return true }); // to limit where the gremlin can touch
 *   toucherGremlin.maxNbTries(5); // How many times the gremlin must look for a touchable element before quitting
 *   toucherGremlin.logger(loggerObject); // inject a logger
 *   toucherGremlin.randomizer(randomizerObject); // inject a randomizer
 *
 * Example usage:
 *
 *   horde.gremlin(gremlins.species.toucher()
 *     .touchTypes(['gesture'])
 *     .positionSelector(function() {
 *        // only touch inside the foo element area
 *        var $el = $('#foo');
 *        var offset = $el.offset();
 *        return [
 *          parseInt(Math.random() * $el.outerWidth() + offset.left),
 *          parseInt(Math.random() * $el.outerHeight() + offset.top)
 *        ];
 *     })
 *     . showAction(function(x, y) {
 *       // do nothing (hide the gremlin action on screen)
 *     })
 *   );
 */
define(function(require) {
	"use strict";

	var configurable = require('../utils/configurable');
	var Chance = require('../vendor/chance');

	return function() {

		var document = window.document,
			body = document.body;

		var defaultTouchTypes = ['tap', 'gesture', 'doubletap', 'tap', 'gesture', 'gesture', 'gesture', 'multitouch', 'multitouch'];

		var defaultPositionSelector = function() {
			return [
				config.randomizer.natural({ max: document.documentElement.clientWidth - 1 }),
				config.randomizer.natural({ max: document.documentElement.clientHeight - 1 })
			];
		};

		var defaultShowAction = function(touches) {
			var fragment = document.createDocumentFragment();
			touches.forEach(function(touch) {
				var touchSignal = document.createElement('div');
				touchSignal.style.border = "3px solid blue";
				touchSignal.style['border-radius'] = '50%'; // Chrome
				touchSignal.style.borderRadius = '50%';     // Mozilla
				touchSignal.style.width = "40px";
				touchSignal.style.height = "40px";
				touchSignal.style['box-sizing'] = 'border-box';
				touchSignal.style.position = "absolute";
				touchSignal.style.webkitTransition = 'opacity .5s ease-out';
				touchSignal.style.mozTransition = 'opacity .5s ease-out';
				touchSignal.style.transition = 'opacity .5s ease-out';
				touchSignal.style.left = (touch.x - 20 ) + 'px';
				touchSignal.style.top = (touch.y - 20 ) + 'px';

				var element = fragment.appendChild(touchSignal);
				setTimeout(function() {
					body.removeChild(element);
				}, 500);
				setTimeout(function() {
					element.style.opacity = 0;
				}, 50);
			});
			document.body.appendChild(fragment);
		};

		var defaultCanTouch = function() {
			return true;
		};

		/**
		 * @mixin
		 */
		var config = {
			touchTypes: defaultTouchTypes,
			positionSelector: defaultPositionSelector,
			showAction: defaultShowAction,
			canTouch: defaultCanTouch,
			maxNbTries: 10,
			logger: {},
			randomizer: new Chance(),
			maxTouches: 2
		};

		/**
		 * generate a list of x/y around the center
		 * @param center
		 * @param points
		 * @param [radius=100]
		 * @param [rotation=0]
		 */
		function getTouches(center, points, radius, degrees) {
			var cx = center[0],
				cy = center[1],
				touches = [],
				slice, i, angle;

			radius = radius || 100;
			degrees = (degrees * Math.PI / 180) || 0;
			slice = 2 * Math.PI / points;

			// just one touch, at the center
			if(points === 1) {
				return [
					{x: cx, y: cy}
				];
			}

			for(i = 0; i < points; i++) {
				angle = (slice * i) + degrees;
				touches.push({
					x: (cx + radius * Math.cos(angle)),
					y: (cy + radius * Math.sin(angle))
				});
			}
			return touches;
		}

		/**
		 * trigger a gesture
		 * @param element
		 * @param startPos
		 * @param startTouches
		 * @param gesture
		 * @param cb
		 */
		function triggerGesture(element, startPos, startTouches, gesture, done) {
			var interval = 10,
				loops = Math.floor(gesture.duration / interval),
				loop = 0;

			triggerTouch(startTouches, element, 'start');

			function gestureLoop() {
				loop++;

				var scale = gesture.radius * (gesture.scale / loops * loop),
					rotation = (gesture.rotation / loops * loop),
					posX = startPos[0] + (gesture.distanceX / loops * loop),
					posY = startPos[1] + (gesture.distanceY / loops * loop),
					touches = getTouches([posX, posY], startTouches.length, scale, rotation),
					is_last = (loop > loops);

				if(!is_last) {
					triggerTouch(touches, element, 'move');
					setTimeout(gestureLoop, 10);
				}
				else {
					triggerTouch(touches, element, 'end');
					done(touches)
				}
			}

			setTimeout(gestureLoop, 10);
		}

		/**
		 * trigger a touchevent
		 * @param touches
		 * @param element
		 * @param type
		 */
		function triggerTouch(touches, element, type) {
			var touchlist = [],
				event = document.createEvent('Event');
			event.initEvent('touch' + type, true, true);

			// just like the mobile browsers, on touchend we dont include a touchlist
			if(type != 'end') {
				touches.forEach(function(touch, i) {
					touchlist.push({
						pageX: touch.x,
						pageY: touch.y,
						clientX: touch.x,
						clientY: touch.y,
						target: element,
						identifier: i
					})
				});
			}

			event.touches = touchlist;
			event.changedTouches = touchlist;

			element.dispatchEvent(event);
			config.showAction(touches);
		}

		/**
		 * diffent kind of gestures
		 */
		var touchTypes = {
			/**
			 * tap, like a click event, only 1 touch
			 * could also be a slow tap, that could turn out to be a hold
			 */
			tap: function(position, element, done) {
				var touches = getTouches(position, 1),
					gesture = {
						duration: config.randomizer.integer({ min: 20, max: 700 })
					};

				triggerTouch(touches, element, 'start');

				setTimeout(function() {
					triggerTouch(touches, element, 'end');
					done(touches, gesture);
				}, gesture.duration);
			},

			/**
			 * doubletap, like a dblclick event, only 1 touch
			 * could also be a slow doubletap, that could turn out to be a hold
			 */
			doubletap: function(position, element, done) {
				touchTypes.tap(position, element, function() {
					setTimeout(function() {
						touchTypes.tap(position, element, done);
					}, 30);
				});
			},

			/**
			 * single touch gesture, could be a drag and swipe, with 1 points
			 */
			gesture: function(position, element, done) {
				var points = 1,
					gesture = {
						radius: config.randomizer.integer({ min: 50, max: 100 }),
						distanceX: config.randomizer.integer({ min: -100, max: 200 }),
						distanceY: config.randomizer.integer({ min: -100, max: 200 }),
						duration: config.randomizer.integer({ min: 20, max: 500 })
					},
					touches = getTouches(position, points, gesture.radius);

				triggerGesture(element, position, touches, gesture, function(touches) {
					done(touches, gesture);
				});
			},

			/**
			 * multitouch gesture, could be a drag, swipe, pinch and rotate, with 2 or more points
			 */
			multitouch: function(position, element, done) {
				var points = config.randomizer.integer({min:2, max:config.maxTouches}),
					gesture = {
						scale: config.randomizer.floating({ min: -1, max: 2 }),
						rotation: config.randomizer.natural({ min: -100, max: 100 }),
						radius: config.randomizer.integer({ min: 50, max: 200 }),
						distanceX: config.randomizer.integer({ min: -20, max: 20 }),
						distanceY: config.randomizer.integer({ min: -20, max: 20 }),
						duration: config.randomizer.integer({ min: 100, max: 1500 })
					},
					touches = getTouches(position, points, gesture.radius);

				triggerGesture(element, position, touches, gesture, function(touches) {
					done(touches, gesture);
				});
			}
		};

		/**
		 * @mixes config
		 */
		function toucherGremlin(done) {
			var position,
				posX, posY,
				targetElement,
				nbTries = 0;

			do {
				position = config.positionSelector();
				posX = position[0];
				posY = position[1];
				targetElement = document.elementFromPoint(posX, posY);
				nbTries++;
				if(nbTries > config.maxNbTries) return;
			} while(!targetElement || !config.canTouch(targetElement));

			var touchType = config.randomizer.pick(config.touchTypes);
			touchTypes[touchType](position, targetElement, logGremlin);

			function logGremlin(touches, details) {
				if(typeof config.showAction == 'function') {
					config.showAction(touches);
				}
				if(typeof config.logger.log == 'function') {
					config.logger.log('gremlin', 'toucher', {
						position: position,
						touchType: touchType,
						gesture: details
					});
				}
				done();
			}
		}

		configurable(toucherGremlin, config);

		return toucherGremlin;
	};
});
<commit_msg>added touch methods/properies, as described by w3c<commit_after>/**
 * The toucher gremlin touches anywhere on the visible area of the document.
 *
 * The toucher gremlin triggers touch events (touchstart, touchmove, touchcancel
 * and touchend), by doing gestures on random targets displayed on the viewport.
 *
 * By default, the touch gremlin activity is showed by a blue circle.
 *
 *   var toucherGremlin = gremlins.species.toucher();
 *   horde.gremlin(toucherGremlin);
 *
 * The toucher gremlin can be customized as follows:
 *
 *   toucherGremlin.touchTypes(['tap', 'gesture']); // the mouse event types to trigger
 *   toucherGremlin.positionSelector(function() { // find a random pair of coordinates to touch });
 *   toucherGremlin.showAction(function(x, y) { // show the gremlin activity on screen });
 *   toucherGremlin.canTouch(function(element) { return true }); // to limit where the gremlin can touch
 *   toucherGremlin.maxNbTries(5); // How many times the gremlin must look for a touchable element before quitting
 *   toucherGremlin.logger(loggerObject); // inject a logger
 *   toucherGremlin.randomizer(randomizerObject); // inject a randomizer
 *
 * Example usage:
 *
 *   horde.gremlin(gremlins.species.toucher()
 *     .touchTypes(['gesture'])
 *     .positionSelector(function() {
 *        // only touch inside the foo element area
 *        var $el = $('#foo');
 *        var offset = $el.offset();
 *        return [
 *          parseInt(Math.random() * $el.outerWidth() + offset.left),
 *          parseInt(Math.random() * $el.outerHeight() + offset.top)
 *        ];
 *     })
 *     . showAction(function(x, y) {
 *       // do nothing (hide the gremlin action on screen)
 *     })
 *   );
 */
define(function(require) {
	"use strict";

	var configurable = require('../utils/configurable');
	var Chance = require('../vendor/chance');

	return function() {

		var document = window.document,
			body = document.body;

		var defaultTouchTypes = ['tap', 'gesture', 'doubletap', 'tap', 'gesture', 'gesture', 'gesture', 'multitouch', 'multitouch'];

		var defaultPositionSelector = function() {
			return [
				config.randomizer.natural({ max: document.documentElement.clientWidth - 1 }),
				config.randomizer.natural({ max: document.documentElement.clientHeight - 1 })
			];
		};

		var defaultShowAction = function(touches) {
			var fragment = document.createDocumentFragment();
			touches.forEach(function(touch) {
				var touchSignal = document.createElement('div');
				touchSignal.style.border = "3px solid blue";
				touchSignal.style['border-radius'] = '50%'; // Chrome
				touchSignal.style.borderRadius = '50%';     // Mozilla
				touchSignal.style.width = "40px";
				touchSignal.style.height = "40px";
				touchSignal.style['box-sizing'] = 'border-box';
				touchSignal.style.position = "absolute";
				touchSignal.style.webkitTransition = 'opacity .5s ease-out';
				touchSignal.style.mozTransition = 'opacity .5s ease-out';
				touchSignal.style.transition = 'opacity .5s ease-out';
				touchSignal.style.left = (touch.x - 20 ) + 'px';
				touchSignal.style.top = (touch.y - 20 ) + 'px';

				var element = fragment.appendChild(touchSignal);
				setTimeout(function() {
					body.removeChild(element);
				}, 500);
				setTimeout(function() {
					element.style.opacity = 0;
				}, 50);
			});
			document.body.appendChild(fragment);
		};

		var defaultCanTouch = function() {
			return true;
		};

		/**
		 * @mixin
		 */
		var config = {
			touchTypes: defaultTouchTypes,
			positionSelector: defaultPositionSelector,
			showAction: defaultShowAction,
			canTouch: defaultCanTouch,
			maxNbTries: 10,
			logger: {},
			randomizer: new Chance(),
			maxTouches: 2
		};

		/**
		 * generate a list of x/y around the center
		 * @param center
		 * @param points
		 * @param [radius=100]
		 * @param [rotation=0]
		 */
		function getTouches(center, points, radius, degrees) {
			var cx = center[0],
				cy = center[1],
				touches = [],
				slice, i, angle;

			radius = radius || 100;
			degrees = (degrees * Math.PI / 180) || 0;
			slice = 2 * Math.PI / points;

			// just one touch, at the center
			if(points === 1) {
				return [
					{x: cx, y: cy}
				];
			}

			for(i = 0; i < points; i++) {
				angle = (slice * i) + degrees;
				touches.push({
					x: (cx + radius * Math.cos(angle)),
					y: (cy + radius * Math.sin(angle))
				});
			}
			return touches;
		}

		/**
		 * trigger a gesture
		 * @param element
		 * @param startPos
		 * @param startTouches
		 * @param gesture
		 * @param cb
		 */
		function triggerGesture(element, startPos, startTouches, gesture, done) {
			var interval = 10,
				loops = Math.floor(gesture.duration / interval),
				loop = 0;

			triggerTouch(startTouches, element, 'start');

			function gestureLoop() {
				loop++;

				var scale = gesture.radius * (gesture.scale / loops * loop),
					rotation = (gesture.rotation / loops * loop),
					posX = startPos[0] + (gesture.distanceX / loops * loop),
					posY = startPos[1] + (gesture.distanceY / loops * loop),
					touches = getTouches([posX, posY], startTouches.length, scale, rotation),
					is_last = (loop > loops);

				if(!is_last) {
					triggerTouch(touches, element, 'move');
					setTimeout(gestureLoop, 10);
				}
				else {
					triggerTouch(touches, element, 'end');
					done(touches)
				}
			}

			setTimeout(gestureLoop, 10);
		}

		/**
		 * trigger a touchevent
		 * @param touches
		 * @param element
		 * @param type
		 */
		function triggerTouch(touches, element, type) {
			var touchlist = [],
				event = document.createEvent('Event');
			event.initEvent('touch' + type, true, true);

			touchlist.identifiedTouch = touchlist.item = function(index) {
				return this[index] || {}
			};

			touches.forEach(function(touch, i) {
				touchlist.push({
					pageX: touch.x,
					pageY: touch.y,
					clientX: touch.x,
					clientY: touch.y,
					screenX: touch.x,
					screenY: touch.y,
					target: element,
					identifier: i
				})
			});

			event.touches = (type == 'end') ? [] : touchlist;
			event.targetTouches = (type == 'end') ? [] : touchlist;
			event.changedTouches = touchlist;

			element.dispatchEvent(event);
			config.showAction(touches);
		}

		/**
		 * diffent kind of gestures
		 */
		var touchTypes = {
			/**
			 * tap, like a click event, only 1 touch
			 * could also be a slow tap, that could turn out to be a hold
			 */
			tap: function(position, element, done) {
				var touches = getTouches(position, 1),
					gesture = {
						duration: config.randomizer.integer({ min: 20, max: 700 })
					};

				triggerTouch(touches, element, 'start');

				setTimeout(function() {
					triggerTouch(touches, element, 'end');
					done(touches, gesture);
				}, gesture.duration);
			},

			/**
			 * doubletap, like a dblclick event, only 1 touch
			 * could also be a slow doubletap, that could turn out to be a hold
			 */
			doubletap: function(position, element, done) {
				touchTypes.tap(position, element, function() {
					setTimeout(function() {
						touchTypes.tap(position, element, done);
					}, 30);
				});
			},

			/**
			 * single touch gesture, could be a drag and swipe, with 1 points
			 */
			gesture: function(position, element, done) {
				var points = 1,
					gesture = {
						radius: config.randomizer.integer({ min: 50, max: 100 }),
						distanceX: config.randomizer.integer({ min: -100, max: 200 }),
						distanceY: config.randomizer.integer({ min: -100, max: 200 }),
						duration: config.randomizer.integer({ min: 20, max: 500 })
					},
					touches = getTouches(position, points, gesture.radius);

				triggerGesture(element, position, touches, gesture, function(touches) {
					done(touches, gesture);
				});
			},

			/**
			 * multitouch gesture, could be a drag, swipe, pinch and rotate, with 2 or more points
			 */
			multitouch: function(position, element, done) {
				var points = config.randomizer.integer({min:2, max:config.maxTouches}),
					gesture = {
						scale: config.randomizer.floating({ min: -1, max: 2 }),
						rotation: config.randomizer.natural({ min: -100, max: 100 }),
						radius: config.randomizer.integer({ min: 50, max: 200 }),
						distanceX: config.randomizer.integer({ min: -20, max: 20 }),
						distanceY: config.randomizer.integer({ min: -20, max: 20 }),
						duration: config.randomizer.integer({ min: 100, max: 1500 })
					},
					touches = getTouches(position, points, gesture.radius);

				triggerGesture(element, position, touches, gesture, function(touches) {
					done(touches, gesture);
				});
			}
		};

		/**
		 * @mixes config
		 */
		function toucherGremlin(done) {
			var position,
				posX, posY,
				targetElement,
				nbTries = 0;

			do {
				position = config.positionSelector();
				posX = position[0];
				posY = position[1];
				targetElement = document.elementFromPoint(posX, posY);
				nbTries++;
				if(nbTries > config.maxNbTries) return;
			} while(!targetElement || !config.canTouch(targetElement));

			var touchType = config.randomizer.pick(config.touchTypes);
			touchTypes[touchType](position, targetElement, logGremlin);

			function logGremlin(touches, details) {
				if(typeof config.showAction == 'function') {
					config.showAction(touches);
				}
				if(typeof config.logger.log == 'function') {
					config.logger.log('gremlin', 'toucher', {
						position: position,
						touchType: touchType,
						gesture: details
					});
				}
				done();
			}
		}

		configurable(toucherGremlin, config);

		return toucherGremlin;
	};
});
<|endoftext|>


<commit_before>/*
 * Copyrights phwang
 * Written by Paul Hwang
 * File name: link_entry_module.js
 */

var util = require("./util_module.js");
var queue = require("./queue_module.js");
var ring = require("./ring_module.js");
var global_link_id = 1;

module.exports = {
    reset: function (link_val, my_name_val) {
        resetIt(link_val, my_name_val);
   },

    malloc: function (my_name_val) {
        link = new LinkEntryObject();
        resetIt(link, my_name_val);
        return link;
    },

    keep_alive: function (link_val) {
        keepAlive(link_val);
    },
};

function resetIt (link_val, my_name_val) {
    link_val.my_name = my_name_val;
    link_val.up_seq = 0;
    link_val.down_seq = 0;
    link_val.queue = queue.malloc();
    link_val.ring = ring.malloc();
    link_val.link_id = global_link_id;
    global_link_id += 1;
}

function keepAlive (link_val) {
    logit("keepAlive", "my_name=" + link_val.my_name + " link_id=" + link_val.link_id);
}

function LinkEntryObject () {
}

function debug (debug_val, str1_val, str2_val) {
    if (debug_val) {
        logit(str1_val, "==" + str2_val);
    }
}

function logit (str1_val, str2_val) {
    return util.utilLogit("NodeMain." + str1_val, str2_val);
}

function abend (str1_val, str2_val) {
    return util.utilAbend("NodeMain." + str1_val, str2_val);
}
<commit_msg>timeout<commit_after>/*
 * Copyrights phwang
 * Written by Paul Hwang
 * File name: link_entry_module.js
 */

var util = require("./util_module.js");
var queue = require("./queue_module.js");
var ring = require("./ring_module.js");
var global_link_id = 1;

module.exports = {
    reset: function (link_val, my_name_val) {
        resetIt(link_val, my_name_val);
   },

    malloc: function (my_name_val) {
        link = new LinkEntryObject();
        resetIt(link, my_name_val);
        return link;
    },

    keep_alive: function (link_val) {
        keepAlive(link_val);
    },
};

function resetIt (link_val, my_name_val) {
    link_val.my_name = my_name_val;
    link_val.up_seq = 0;
    link_val.down_seq = 0;
    link_val.queue = queue.malloc();
    link_val.ring = ring.malloc();
    link_val.link_id = global_link_id;
    global_link_id += 1;
    link_val.keep_alive_timer = resetTimeout(link_val);
}

function resetTimeout (link_val) {
    if (link_val.keep_alive_timer) {
        clearInterval(link_val.keep_alive_timer);
    }
    logit("resetTimeout", "my_name=" + link_val.my_name + " link_id=" + link_val.link_id);
    var time_out = setInterval(function () {
        console.log("===in time out");
    }, 20000);
    return time_out;
}

function keepAlive (link_val) {
    //logit("keepAlive", "my_name=" + link_val.my_name + " link_id=" + link_val.link_id);
    link_val.keep_alive_timer = resetTimeout(link_val);
}

function LinkEntryObject () {
}

function debug (debug_val, str1_val, str2_val) {
    if (debug_val) {
        logit(str1_val, "==" + str2_val);
    }
}

function logit (str1_val, str2_val) {
    return util.utilLogit("NodeMain." + str1_val, str2_val);
}

function abend (str1_val, str2_val) {
    return util.utilAbend("NodeMain." + str1_val, str2_val);
}
<|endoftext|>


<commit_before>var InterfaceFetcher = require('./spotify-interface-fetcher');

module.exports = function(robot) {

    var interfaceFetcher = new InterfaceFetcher('spotify', 'org.mpris.MediaPlayer2.spotify', '/org/mpris/MediaPlayer2', 'org.mpris.MediaPlayer2.Player');

    robot.respond(/sp pause$/i, function(msg) {
        interfaceFetcher.getInterface().then(
            function(iface) {
                iface.Pause();
                msg.send(":pause:");
            },
            function(err) {
                msg.send("Error: " + err);
            }
        );
    });

    robot.respond(/sp play$/i, function(msg) {
        interfaceFetcher.getInterface().then(
            function(iface) {
                iface.PlayPause();
                msg.send(":playpause:");
            },
            function(err) {
                msg.send("Error: " + err);
            }
        );
    });

    robot.respond(/sp next$/i, function(msg) {
        interfaceFetcher.getInterface().then(
            function(iface) {
                iface.Next();
                msg.send(":next:");
            },
            function(err) {
                msg.send("Error: " + err);
            }
        );
    });

    robot.respond(/sp prev$/i, function(msg) {
        interfaceFetcher.getInterface().then(
            function(iface) {
                iface.Previous();
                iface.Previous();
                msg.send(":previous");
            },
            function(err) {
                msg.send("Error: " + err);
            }
        );
    });

    robot.respond(/sp open (.*?)$/i, function(msg) {
        interfaceFetcher.getInterface().then(
            function(iface) {
                iface.OpenUri(msg.match[1]);
            },
            function(err) {
                msg.send("Error: " + err);
            }
        );
    });

    robot.respond(/sp radio (.*?)$/i, function(msg) {
        var seed = msg.match[1];
        var seedArray = seed.split(":");
        var uriLength = seedArray.length;
        if ((uriLength > 1) && (seedArray[0] === "spotify")) {
            var identifier = seedArray[uriLength - 1];
            var type = seedArray[uriLength - 2];
            var link = "spotify:app:radio:" + type + ":" + identifier;
            interfaceFetcher.getInterface().then(
                function(iface) {
                    iface.OpenUri(link);
                    msg.send("Started the :radio:");
                },
                function(err) {
                    msg.send("Error: " + err);
                }
            );
        } else {
            msg.send('Not a valid spotify URL, must start with `spotify:`');
        }
    });
};

<commit_msg>doc: add script doc block<commit_after>// Description:
//   Control spotify on linux via DBus
//
// Dependencies:
//   dbus: ~0.2
//   pidof: ~1.0
//   properties: ~1.2
//   q: ~1.0
//
// Configuration:
//   None
//
//// Commands:
//   hubot sp pause
//   hubot sp play
//   hubot sp next
//   hubot sp prev
//   hubot sp open <URI>
//   hubot sp radio <URI>
//
//  Notes:
//    Hubot should be running on the same machine and user as the spotify process
//
// Author:
//   Eluinhost

var InterfaceFetcher = require('./spotify-interface-fetcher');

module.exports = function(robot) {

    var interfaceFetcher = new InterfaceFetcher('spotify', 'org.mpris.MediaPlayer2.spotify', '/org/mpris/MediaPlayer2', 'org.mpris.MediaPlayer2.Player');

    robot.respond(/sp pause$/i, function(msg) {
        interfaceFetcher.getInterface().then(
            function(iface) {
                iface.Pause();
                msg.send(":pause:");
            },
            function(err) {
                msg.send("Error: " + err);
            }
        );
    });

    robot.respond(/sp play$/i, function(msg) {
        interfaceFetcher.getInterface().then(
            function(iface) {
                iface.PlayPause();
                msg.send(":playpause:");
            },
            function(err) {
                msg.send("Error: " + err);
            }
        );
    });

    robot.respond(/sp next$/i, function(msg) {
        interfaceFetcher.getInterface().then(
            function(iface) {
                iface.Next();
                msg.send(":next:");
            },
            function(err) {
                msg.send("Error: " + err);
            }
        );
    });

    robot.respond(/sp prev$/i, function(msg) {
        interfaceFetcher.getInterface().then(
            function(iface) {
                iface.Previous();
                iface.Previous();
                msg.send(":previous");
            },
            function(err) {
                msg.send("Error: " + err);
            }
        );
    });

    robot.respond(/sp open (.*?)$/i, function(msg) {
        interfaceFetcher.getInterface().then(
            function(iface) {
                iface.OpenUri(msg.match[1]);
            },
            function(err) {
                msg.send("Error: " + err);
            }
        );
    });

    robot.respond(/sp radio (.*?)$/i, function(msg) {
        var seed = msg.match[1];
        var seedArray = seed.split(":");
        var uriLength = seedArray.length;
        if ((uriLength > 1) && (seedArray[0] === "spotify")) {
            var identifier = seedArray[uriLength - 1];
            var type = seedArray[uriLength - 2];
            var link = "spotify:app:radio:" + type + ":" + identifier;
            interfaceFetcher.getInterface().then(
                function(iface) {
                    iface.OpenUri(link);
                    msg.send("Started the :radio:");
                },
                function(err) {
                    msg.send("Error: " + err);
                }
            );
        } else {
            msg.send('Not a valid spotify URL, must start with `spotify:`');
        }
    });
};

<|endoftext|>


<commit_before>/* @flow */
import React, { PureComponent } from 'react';
import { View, Image, StyleSheet } from 'react-native';

import { RawLabel } from '../common';

const styles = StyleSheet.create({
  description: {
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 10,
  },
  icon: {
    width: 25,
    height: 25,
    marginRight: 10,
  },
  name: {
    fontSize: 20,
  },
});

type Props = {
  name: string,
  iconUrl: string,
};

export default class RealmInfo extends PureComponent<Props> {
  props: Props;

  render() {
    const { name, iconUrl } = this.props;

    return (
      <View style={styles.description}>
        {iconUrl && <Image style={styles.icon} source={{ uri: iconUrl }} />}
        <RawLabel style={styles.name} text={name} />
      </View>
    );
  }
}
<commit_msg>RealmInfo: Replace legacy grid dimensions<commit_after>/* @flow */
import React, { PureComponent } from 'react';
import { View, Image, StyleSheet } from 'react-native';

import { RawLabel } from '../common';

const styles = StyleSheet.create({
  description: {
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    marginBottom: 8,
  },
  icon: {
    width: 24,
    height: 24,
    marginRight: 8,
  },
  name: {
    fontSize: 20,
  },
});

type Props = {
  name: string,
  iconUrl: string,
};

export default class RealmInfo extends PureComponent<Props> {
  props: Props;

  render() {
    const { name, iconUrl } = this.props;

    return (
      <View style={styles.description}>
        {iconUrl && <Image style={styles.icon} source={{ uri: iconUrl }} />}
        <RawLabel style={styles.name} text={name} />
      </View>
    );
  }
}
<|endoftext|>


<commit_before>(function () {
    var mixins = (function () {
        var extendWith = {};

        /**
         * Formats a string with given parameters.
         * Usage:
         *    _.format('Other {} are {}', 'people', 'good plumbers')
         * Produces:
         *    'Other people are good plumbers'
         *
         * @param {String} template - the template to format
         * @returns {String} the formatted string
         */
        extendWith.format = function (template) {
            var i = 1,
                args = arguments;
            return template.replace(/{}/g, function () {
                return typeof args[i] !== 'undefined' ? args[i++] : '';
            });
        };

        /**
         * Formats a url with given parameters.
         * Usage:
         *    _.formatUrl('/:categ/:id', {categ: 'books', isbn: '034038204X'})
         * Produces:
         *    '/categ/books/034038204X'
         *
         * @param {String} template - the url template to format
         * @param {Object} params - the replacement parameters
         * @returns {String} the formatted url
         */
        extendWith.formatUrl = function (template, params) {
            return template.replace(/:[a-z0-9]+/g, function (a, b) {
                var paramName = a.slice(1, a.length);
                var paramVal = params[paramName];
                return typeof paramVal !== 'undefined' ? paramVal : '';
            });
        };

        /**
         * Merges two objects without mutating the two objects
         * Usage:
         *    var src = {a: 1, b: 2};
         *    var dest = {c: 3, d: 4};
         *    var merged = _.immutableMerge(src, dest);
         * Produces:
         *    src -> { a: 1, b: 2 }
         *    dest -> { c: 3, d: 4 }
         *    merged -> { a: 1, b: 2, c: 3, d: 4 }
         * @param {Object} src - the source object
         * @param {Object} dest - the destination object
         * @returns {Object} a new object constructed from properties of src and dest
         */
        extendWith.immutableMerge = function (src, dest) {
            return _.merge(_.cloneDeep(src), dest);
        };

        return extendWith;
    })();

    /**
     * bootstrap mixins for node and the browser
     * For the browser: lodash must be explicitly included above
     *    this library
     * For node: this library will wrap lodash so there is no
     *    need to include lodash
     */

    if (typeof module !== 'undefined' &&
        typeof module.exports !== 'undefined') {
        // node environment
        var _ = require('lodash');
        _.mixin(mixins);
        module.exports = _;
    }
    else {
        // browser environment
        if (typeof _ !== 'undefined') {
            _.mixin(mixins);
        }
        else {
            throw {
                name: 'Error',
                message: 'lodash must be included before lodash-extensions.'
            }
        }
    }
})();

<commit_msg>Fix bootstrap for browser<commit_after>(function () {
    var mixins = (function () {
        var extendWith = {};

        /**
         * _.immutableMerge
         *
         * Merges two objects without mutating the two objects
         * Usage:
         *    var src = {a: 1, b: 2};
         *    var dest = {c: 3, d: 4};
         *    var merged = _.immutableMerge(src, dest);
         * Produces:
         *    src -> { a: 1, b: 2 }
         *    dest -> { c: 3, d: 4 }
         *    merged -> { a: 1, b: 2, c: 3, d: 4 }
         * @param {Object} src - the source object
         * @param {Object} dest - the destination object
         * @returns {Object} a new object constructed from properties of src and dest
         */
        extendWith.immutableMerge = function (src, dest) {
            return _.merge(_.cloneDeep(src), dest);
        };

        /**
         * _.format
         *
         * Formats a string with given parameters.
         * Usage:
         *    _.format('Other {} are {}', 'people', 'good plumbers')
         * Produces:
         *    'Other people are good plumbers'
         *
         * @param {String} template - the template to format
         * @returns {String} the formatted string
         */
        extendWith.format = function (template) {
            var i = 1,
                args = arguments;
            return template.replace(/{}/g, function () {
                return typeof args[i] !== 'undefined' ? args[i++] : '';
            });
        };

        /**
         * _.formatUrl
         *
         * Formats a url with given parameters.
         * Usage:
         *    _.formatUrl('/:categ/:id', {categ: 'books', isbn: '034038204X'})
         * Produces:
         *    '/categ/books/034038204X'
         *
         * @param {String} template - the url template to format
         * @param {Object} params - the replacement parameters
         * @returns {String} the formatted url
         */
        extendWith.formatUrl = function (template, params) {
            return template.replace(/:[a-z0-9]+/g, function (a, b) {
                var paramName = a.slice(1, a.length);
                var paramVal = params[paramName];
                return typeof paramVal !== 'undefined' ? paramVal : '';
            });
        };

        return extendWith;
    })();

    /**
     * bootstrap mixins for node and the browser
     * For the browser: lodash must be explicitly included above
     *    this library
     * For node: this library will wrap lodash so there is no
     *    need to include lodash
     */

    // browser environment
    var _;
    if (typeof module !== 'undefined' &&
        typeof module.exports !== 'undefined') {
        // node environment
        _ = require('lodash');
        _.mixin(mixins);
        module.exports = _;
    }
    else {
        _ = window._;
        if (typeof _ === 'function') {
            _.mixin(mixins);
        }
        else {
            throw new Error('lodash must be included before lodash-extensions.');
        }
    }
})();
<|endoftext|>


<commit_before>//The store is an event emitter.
const EventEmitter = require('events').EventEmitter;
const assign = require('object-assign');
const {isArray, isEmpty, isObject, isFunction} = require('lodash/lang');
const {defer} = require('lodash/function');
const capitalize = require('lodash/string/capitalize');
const Immutable = require('immutable');
const AppDispatcher = require('../dispatcher');
const _instances = [];

/**
* @class CoreStore
*/
class CoreStore extends EventEmitter {

    /**
    * Contructor of the store class.
    */
    constructor(config) {
        super();
        assign(this, {
            config
        });
        //Initialize the data as immutable map.
        this.data = Immutable.Map({});
        this.status = Immutable.Map({});
        this.error = Immutable.Map({});
        this.pendingEvents = [];
        this.customHandler = assign({}, config.customHandler);
        //Register all gernerated methods.
        this.buildDefinition();
        this.buildEachNodeChangeEventListener();
        this.registerDispatcher();
        if(!!__DEV__){
          this._registerDevTools();
        }
    }
    // Get all the instances of core store.
    get _instances(){
      return [..._instances];
    }
    // register the instances saving
    _registerDevTools(){
        _instances.push(this);
    }
    /**
    * Initialize the store configuration.
    * @param {object} storeConfiguration - The store configuration for the initialization.
    */
    buildDefinition() {
        /**
        * Build the definitions for the entity (may be a subject.)
        * @type {object}
        */
        if(!this.config.definition){
            throw new Error('Core Store: missing definition', this.config);
        }
        this.definition = this.config.definition;
        return this.definition;
    }
    /**
    * Get the whole value of the
    * @return {[type]} [description]
    */
    getValue(){
        return this.data ? this.data.toJS() : {};
    }
    /**
    * Getter on the identifier property.
    * @return {string} - Store identifier.
    */
    get identifier(){
        return this.config && this.config.identifier ? this.config.identifier : undefined;
    }
    /** Return the status of a definition.
    * @param {string} - The definition to load.
    * @returns {string} - The status of a definition.
    */
    getStatus(def){
        if (this.status.has(def)){
            return this.status.get(def);
        }
        return undefined;
    }
    /**
    * Emit all events pending in the pendingEvents map.
    */
    emitPendingEvents(){
        this.pendingEvents.map((evtToEmit)=>{
            let {name, data} = evtToEmit;
            this.emit(name, data);
        });
    }

    /**
    * Replace the emit function with a willEmit in otder to store the changing event but send it afterwards.
    * @param eventName {string} - The event name.
    * @param  data {object} - The event's associated data.
    */
    willEmit(eventName, data){
        this.pendingEvents = this.pendingEvents.reduce((result, current)=>{
            if(current.name !== eventName){
                result.push(current);
            }
            return result;
        }, [{name: eventName, data: data}]);
    }

    /**
    * Clear all pending events.
    */
    clearPendingEvents(){
        this.pendingEvents = [];
    }
    /**
    * Build a change listener for each property in the definition. (should be macro entities);
    */
    buildEachNodeChangeEventListener() {
        const currentStore = this;
        //Loop through each store properties.
        for (let definition in this.definition) {
            const capitalizeDefinition = capitalize(definition);
            //Creates the change listener
            currentStore[`add${capitalizeDefinition}ChangeListener`] = function(def){
                return function (cb) {
                    currentStore.addListener(`${def}:change`, cb);
                }
            }(definition);
            //Remove the change listener
            currentStore[`remove${capitalizeDefinition}ChangeListener`] = function(def){
                return function (cb) {
                    currentStore.removeListener(`${def}:change`, cb);
                }
            }(definition);
            //Create an update method.
            //Should be named updateData to be more explicit
            if(currentStore[`update${capitalizeDefinition}`] === undefined){
                currentStore[`update${capitalizeDefinition}`] = function(def){
                    return function (dataNode, status, informations) {
                        const immutableNode = isFunction(dataNode) ? dataNode : Immutable.fromJS(dataNode);
                        currentStore.data = currentStore.data.set(def, immutableNode);
                        //Update the status on the data.
                        currentStore.status = currentStore.status.set(def, status);

                        currentStore.willEmit(`${def}:change`, {property: def, status: status, informations: informations});
                    }
                }(definition);
            }

            //Create a get method.
            if(currentStore[`get${capitalizeDefinition}`] === undefined){
                currentStore[`get${capitalizeDefinition}`] = function(def){
                    return function () {
                        const hasData = currentStore.data.has(def);
                        if(hasData){
                            const rawData = currentStore.data.get(def);
							if(rawData && rawData.toJS){
								const data = rawData.toJS();
								return data;
							}
							return rawData;
                        }
                        return undefined;
                    };
                }(definition);
            }
            //Creates the error change listener
            currentStore[`add${capitalizeDefinition}ErrorListener`] = function(def){
                return function (cb) {
                    currentStore.addListener(`${def}:error`, cb);
                }
            }(definition);
            //Remove the change listener
            currentStore[`remove${capitalizeDefinition}ErrorListener`] = function(def){
                return function (cb) {
                    currentStore.removeListener(`${def}:error`, cb);
                }
            }(definition);
            //Create an update method.
            currentStore[`updateError${capitalizeDefinition}`] = function(def){
                return function (dataNode, status, informations) {
                    //CheckIsObject
                    const immutableNode = Immutable[isArray(dataNode) ? "List" : "Map"](dataNode);
                    currentStore.error = currentStore.error.set(def, immutableNode);
                    currentStore.status = currentStore.status.set(def, status);
                    currentStore.willEmit(`${def}:error`, {property: def, status: status, informations: informations});
                }
            }(definition);
            //Create a get method.
            currentStore[`getError${capitalizeDefinition}`] = function(def){
                return function(){
                    const hasData = currentStore.error.has(def);
                    return hasData ? currentStore.error.get(def).toJS() : undefined;
                };
            }(definition);

            // status
            currentStore[`add${capitalizeDefinition}StatusListener`] = function(def){
                return function (cb) {
                    currentStore.addListener(`${def}:status`, cb);
                }
            }(definition);
            //Remove the change listener
            currentStore[`remove${capitalizeDefinition}StatusListener`] = function(def){
                return function (cb) {
                    currentStore.removeListener(`${def}:status`, cb);
                }
            }(definition);
            //Create an update method.
            currentStore[`updateStatus${capitalizeDefinition}`] = function(def){
                return function updateStatus(dataNode, status, informations) {
                    //CheckIsObject
                    //console.log(`status  ${JSON.stringify(status)}`);
                    const statusNode = status;//Immutable.fromJS(status); // mMaybe it is a part of the status only.
                    currentStore.status = currentStore.status.set(def, statusNode);
                    currentStore.willEmit(`${def}:status`, {property: def, status: status, informations: informations});
                }
            }(definition);
            //Create a get method.
            currentStore[`getStatus${capitalizeDefinition}`] = function(def){
                return function getStatus(){
                    const hasData = currentStore.status.has(def);
                    const data = hasData ? currentStore.status.get(def) : undefined;
                    return data.toJS ? data.toJS() : data;
                };
            }(definition);
        }
    }

    delayPendingEvents(context){
        //Delay all the change emit by the store to be sure it is done after the internal store propagation and to go out of the dispatch function.
        defer(()=>{
            context.emitPendingEvents();
            context.clearPendingEvents();
        });
    }
    _buildInformations(incomingInfos){
        return {
            callerId: incomingInfos.action.callerId
        };
    }
    /**
    * The store registrer itself on the dispatcher.
    */
    registerDispatcher(){
        const currentStore = this;
        this.dispatch = AppDispatcher.register(function(transferInfo) {
            //Check if an identifier check is necessary.
            if(currentStore.identifier){
                //If an identifier is needed a check is triggered.
                if(!transferInfo || !transferInfo.action || !transferInfo.action.identifier || transferInfo.action.identifier !== currentStore.identifier){
                    return;
                }
            }
            //currentStore.clearPendingEvents();
            if(currentStore.globalCustomHandler){
                return currentStore.globalCustomHandler.call(currentStore, transferInfo);
            }

            //Read data from the action transfer information.
            const rawData = transferInfo.action.data;
            const status = transferInfo.action.status || {};
            const type = transferInfo.action.type;
            const otherInformations = currentStore._buildInformations(transferInfo);

            //Call each node handler for the matching definition's node.
            for(let node in rawData){
                if(currentStore.definition[node]){
                    //Call a custom handler if this exists.
                    if(currentStore.customHandler && currentStore.customHandler[node] && currentStore.customHandler[node][type]){
                        currentStore.customHandler[node][type].call(currentStore, rawData[node], status[node], otherInformations);
                    }else {
                        //Update the data for the given node. and emit the change/.
                        if(!isFunction(currentStore[`${type}${capitalize(node)}`])){
                            throw new Error(`The listener you try to call is unavailable : ${type}${capitalize(node)}` );
                        }
                        currentStore[`${type}${capitalize(node)}`](rawData[node], status[node], otherInformations);
                    }
                }
            }
            currentStore.delayPendingEvents(currentStore);
        });
    }
    /**
    * Add a listener on a store event.
    * @param {string}   eventName - Event name.
    * @param {Function} cb - CallBack to call on the event change name.
    */
    addListener(eventName, cb) {
        this.on(eventName, cb);
    }
}
module.exports = CoreStore;
<commit_msg>CoreStore - Preventing silent name collisions<commit_after>import { EventEmitter } from 'events';
import assign from 'object-assign';
import { isArray, isEmpty, isObject, isFunction } from 'lodash/lang';
import { defer } from 'lodash/function';
import intersection from 'lodash/array/intersection';
import capitalize from 'lodash/string/capitalize';
import Immutable from 'immutable';
import AppDispatcher from '../dispatcher';

const reservedNames = ["Error", "Status"];

const _instances = [];

/**
* @class CoreStore
*/
class CoreStore extends EventEmitter {

    /**
    * Contructor of the store class.
    */
    constructor(config) {
        super();
        assign(this, {
            config
        });
        //Initialize the data as immutable map.
        this.data = Immutable.Map({});
        this.status = Immutable.Map({});
        this.error = Immutable.Map({});
        this.pendingEvents = [];
        this.customHandler = assign({}, config.customHandler);
        //Register all gernerated methods.
        this.buildDefinition();
        this.buildEachNodeChangeEventListener();
        this.registerDispatcher();
        if (!!__DEV__) {
            this._registerDevTools();
        }
    }
    // Get all the instances of core store.
    get _instances() {
        return [..._instances];
    }
    // register the instances saving
    _registerDevTools() {
        _instances.push(this);
    }
    /**
    * Initialize the store configuration.
    * @param {object} storeConfiguration - The store configuration for the initialization.
    */
    buildDefinition() {
        /**
        * Build the definitions for the entity (may be a subject.)
        * @type {object}
        */
        if (!this.config.definition) {
            throw new Error('Core Store: missing definition', this.config);
        }
        this.definition = this.config.definition;

        const properties = Object.keys(this.definition).map(elt => capitalize(elt));
        const reservedProperties = properties.reduce((acc, elt) => acc.concat(reservedNames.map(w => w + elt)), []);
        if (intersection(properties, reservedProperties).length > 0) {
            throw new Error(`You have a name collision into this store : you cannot use a node named Blabla and anoter named ErrorBlabla, or StatusBlabla : ${intersection(properties, reservedProperties).join(' ')} `);
        }
        return this.definition;
    }
    /**
    * Get the whole value of the
    * @return {[type]} [description]
    */
    getValue() {
        return this.data ? this.data.toJS() : {};
    }
    /**
    * Getter on the identifier property.
    * @return {string} - Store identifier.
    */
    get identifier() {
        return this.config && this.config.identifier ? this.config.identifier : undefined;
    }
    /** Return the status of a definition.
    * @param {string} - The definition to load.
    * @returns {string} - The status of a definition.
    */
    getStatus(def) {
        if (this.status.has(def)) {
            return this.status.get(def);
        }
        return undefined;
    }
    /**
    * Emit all events pending in the pendingEvents map.
    */
    emitPendingEvents() {
        this.pendingEvents.map((evtToEmit) => {
            let { name, data } = evtToEmit;
            this.emit(name, data);
        });
    }

    /**
    * Replace the emit function with a willEmit in otder to store the changing event but send it afterwards.
    * @param eventName {string} - The event name.
    * @param  data {object} - The event's associated data.
    */
    willEmit(eventName, data) {
        this.pendingEvents = this.pendingEvents.reduce((result, current) => {
            if (current.name !== eventName) {
                result.push(current);
            }
            return result;
        }, [{ name: eventName, data: data }]);
    }

    /**
    * Clear all pending events.
    */
    clearPendingEvents() {
        this.pendingEvents = [];
    }
    /**
    * Build a change listener for each property in the definition. (should be macro entities);
    */
    buildEachNodeChangeEventListener() {
        const currentStore = this;
        //Loop through each store properties.
        for (let definition in this.definition) {
            const capitalizeDefinition = capitalize(definition);
            //Creates the change listener
            currentStore[`add${capitalizeDefinition}ChangeListener`] = function (def) {
                return function (cb) {
                    currentStore.addListener(`${def}:change`, cb);
                }
            }(definition);
            //Remove the change listener
            currentStore[`remove${capitalizeDefinition}ChangeListener`] = function (def) {
                return function (cb) {
                    currentStore.removeListener(`${def}:change`, cb);
                }
            }(definition);
            //Create an update method.
            //Should be named updateData to be more explicit
            if (currentStore[`update${capitalizeDefinition}`] === undefined) {
                currentStore[`update${capitalizeDefinition}`] = function (def) {
                    return function (dataNode, status, informations) {
                        const immutableNode = isFunction(dataNode) ? dataNode : Immutable.fromJS(dataNode);
                        currentStore.data = currentStore.data.set(def, immutableNode);
                        //Update the status on the data.
                        currentStore.status = currentStore.status.set(def, status);

                        currentStore.willEmit(`${def}:change`, { property: def, status: status, informations: informations });
                    }
                }(definition);
            }

            //Create a get method.
            if (currentStore[`get${capitalizeDefinition}`] === undefined) {
                currentStore[`get${capitalizeDefinition}`] = function (def) {
                    return function () {
                        const hasData = currentStore.data.has(def);
                        if (hasData) {
                            const rawData = currentStore.data.get(def);
                            if (rawData && rawData.toJS) {
                                const data = rawData.toJS();
                                return data;
                            }
                            return rawData;
                        }
                        return undefined;
                    };
                }(definition);
            }
            //Creates the error change listener
            currentStore[`add${capitalizeDefinition}ErrorListener`] = function (def) {
                return function (cb) {
                    currentStore.addListener(`${def}:error`, cb);
                }
            }(definition);
            //Remove the change listener
            currentStore[`remove${capitalizeDefinition}ErrorListener`] = function (def) {
                return function (cb) {
                    currentStore.removeListener(`${def}:error`, cb);
                }
            }(definition);
            //Create an update method.
            currentStore[`updateError${capitalizeDefinition}`] = function (def) {
                return function (dataNode, status, informations) {
                    //CheckIsObject
                    const immutableNode = Immutable[isArray(dataNode) ? "List" : "Map"](dataNode);
                    currentStore.error = currentStore.error.set(def, immutableNode);
                    currentStore.status = currentStore.status.set(def, status);
                    currentStore.willEmit(`${def}:error`, { property: def, status: status, informations: informations });
                }
            }(definition);
            //Create a get method.
            currentStore[`getError${capitalizeDefinition}`] = function (def) {
                return function () {
                    const hasData = currentStore.error.has(def);
                    return hasData ? currentStore.error.get(def).toJS() : undefined;
                };
            }(definition);

            // status
            currentStore[`add${capitalizeDefinition}StatusListener`] = function (def) {
                return function (cb) {
                    currentStore.addListener(`${def}:status`, cb);
                }
            }(definition);
            //Remove the change listener
            currentStore[`remove${capitalizeDefinition}StatusListener`] = function (def) {
                return function (cb) {
                    currentStore.removeListener(`${def}:status`, cb);
                }
            }(definition);
            //Create an update method.
            currentStore[`updateStatus${capitalizeDefinition}`] = function (def) {
                return function updateStatus(dataNode, status, informations) {
                    //CheckIsObject
                    //console.log(`status  ${JSON.stringify(status) }`);
                    const statusNode = status;//Immutable.fromJS(status); // mMaybe it is a part of the status only.
                    currentStore.status = currentStore.status.set(def, statusNode);
                    currentStore.willEmit(`${def}:status`, { property: def, status: status, informations: informations });
                }
            }(definition);
            //Create a get method.
            currentStore[`getStatus${capitalizeDefinition}`] = function (def) {
                return function getStatus() {
                    const hasData = currentStore.status.has(def);
                    const data = hasData ? currentStore.status.get(def) : undefined;
                    return data.toJS ? data.toJS() : data;
                };
            }(definition);
        }
    }

    delayPendingEvents(context) {
        //Delay all the change emit by the store to be sure it is done after the internal store propagation and to go out of the dispatch function.
        defer(() => {
            context.emitPendingEvents();
            context.clearPendingEvents();
        });
    }
    _buildInformations(incomingInfos) {
        return {
            callerId: incomingInfos.action.callerId
        };
    }
    /**
    * The store registrer itself on the dispatcher.
    */
    registerDispatcher() {
        const currentStore = this;
        this.dispatch = AppDispatcher.register(function (transferInfo) {
            //Check if an identifier check is necessary.
            if (currentStore.identifier) {
                //If an identifier is needed a check is triggered.
                if (!transferInfo || !transferInfo.action || !transferInfo.action.identifier || transferInfo.action.identifier !== currentStore.identifier) {
                    return;
                }
            }
            //currentStore.clearPendingEvents();
            if (currentStore.globalCustomHandler) {
                return currentStore.globalCustomHandler.call(currentStore, transferInfo);
            }

            //Read data from the action transfer information.
            const rawData = transferInfo.action.data;
            const status = transferInfo.action.status || {};
            const type = transferInfo.action.type;
            const otherInformations = currentStore._buildInformations(transferInfo);

            //Call each node handler for the matching definition's node.
            for (let node in rawData) {
                if (currentStore.definition[node]) {
                    //Call a custom handler if this exists.
                    if (currentStore.customHandler && currentStore.customHandler[node] && currentStore.customHandler[node][type]) {
                        currentStore.customHandler[node][type].call(currentStore, rawData[node], status[node], otherInformations);
                    } else {
                        //Update the data for the given node. and emit the change/.
                        if (!isFunction(currentStore[`${type}${capitalize(node)}`])) {
                            throw new Error(`The listener you try to call is unavailable : ${type} ${capitalize(node)} `);
                        }
                        currentStore[`${type} ${capitalize(node)} `](rawData[node], status[node], otherInformations);
                    }
                }
            }
            currentStore.delayPendingEvents(currentStore);
        });
    }
    /**
    * Add a listener on a store event.
    * @param {string}   eventName - Event name.
    * @param {Function} cb - CallBack to call on the event change name.
    */
    addListener(eventName, cb) {
        this.on(eventName, cb);
    }
}
export default CoreStore;
<|endoftext|>