zoukankan      html  css  js  c++  java
  • File System 之本地文件系统

    上一篇文章提到了,最近做一个基于 File System/IndexedDB的应用,上一篇是定额和使用的查询。

    因为LocalFileSystem只有chrome支持,有点尴尬,如果按需加载又何来尴尬。

    这一篇是关于文件和目录的操作的,怕陷入回调陷阱,基于promise和ES7的await。

    首先介绍两个函数:

    第一个是 :toPromise,把那种带成功失败回调的函数转化为Promise,因为File API的File System里面很多方法都是这种格式的。
    /**
     * 转为promise,主要是把 a.b(param1,param2,successCallback,errorCall) 转为promise
     * @param {*期待的是函数} obj 
     * @param {*上下文} ctx 
     * @param {*参数} args 
     */
    function toPromise(obj, ctx = window, ...args) {
        if (!obj) return obj
    
        //如果已经是Promise对象
        if ('function' == typeof obj.then) return obj
    
        //若obj是函数直接转换
        if ('function' == typeof obj) return _toPromise(obj)
    
        return obj;
    
        //函数转成 promise
        function _toPromise(fn) {
            return new Promise((resolve, reject) => {
    
                fn.call(ctx, ...args, (...ags) => {
                    //多个参数返回数组,单个直接返回对象
                    resolve(ags && ags.length > 1 ? ags : ags[0] || null)
                }, (err) => {
                    reject(err)
                })
    
            })
        }
    }
    

     第二个是 promiseForEach,顺序的执行多个Promise,思想也就是then的拼接

    /**
     * https://segmentfault.com/q/1010000007499416
     * Promise for forEach
     * @param {*数组} arr 
     * @param {*回调} cb(val)返回的应该是Promise 
     * @param {*是否需要执行结果集} needResults
     */
    function promiseForEach(arr, cb, needResults) {
        let realResult = [], lastResult //lastResult参数暂无用
        let result = Promise.resolve()
        Array.from(arr).forEach((val, index) => {
            result = result.then(() => {
                return cb(val, index).then((res) => {
                    lastResult = res
                    needResults && realResult.push(res)
                })
            })
        })
    
        return needResults ? result.then(() => realResult) : result
    }
    

     

    这两个方法完毕后,就直接上主体代码了, hold on。

    /**
     * 参考的API:
     * http://w3c.github.io/quota-api/
     * 
     */
    
    if (!window.location.origin) {
        window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : '');
    }
    //文件系统请求标识 
    window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem
    //根据URL取得文件的读取权限 
    window.resolveLocalFileSystemURL = window.resolveLocalFileSystemURL || window.webkitResolveLocalFileSystemURL
    
    //临时储存和永久存储
    navigator.temporaryStorage = navigator.temporaryStorage || navigator.webkitTemporaryStorage;
    navigator.persistentStorage = navigator.persistentStorage || navigator.webkitPersistentStorage;
    
    //常量
    const _TEMPORARY = 'temporary',
        _PERSISTENT = 'persistent',
        FS_SCHEME = 'filesystem:'
    
    
    class LocalFileSystem {
    
        constructor(fs) {
            this._fs = fs       //文件系统
            this._root = fs.root //文件系统的根Entry
            this._instance = null //示例对象
            this._type = null //类型,window.TEMPORAR| window.PERSISTENT
            this._fsBaseUrl = null //文件系统的基础地址
        }
    
    
        /**
           * 
           * @param {* window.TEMPORAR(0) |window.PERSISTENT(1)} type
           * @param {* 申请空间大小,单位为M } size
           */
        static getInstance(type = window.TEMPORARY, size = 1) {
    
            if (this._instance) {
                return Promise.resolve(this._instance)
            }
            //类型
            let typeValue = type,
                //文件系统基础地址
                fsBaseUrl = FS_SCHEME + location.origin + '/' + (type == 1 ? _PERSISTENT : _TEMPORARY) + '/'
            return new Promise((resolve, reject) => {
                window.requestFileSystem(type, size * 1024 * 1024, fs => {
                    this._instance = new LocalFileSystem(fs)
                    this._instance._type = typeValue;
                    this._instance._fsBaseUrl = fsBaseUrl
                    return resolve(this._instance)
                }, (err) => reject(err))
            })
    
        }
    
        /**
         * 获得FileEntry
         * @param {*文件路径} path  
         */
        _getFileEntry(path, create = false) {
            return toPromise(this._root.getFile, this._root, path, { create, exclusive: false })
        }
    
        /**
         * 获取目录
         * @param {*路径} path 
         * @param {*不存在的时候是否创建} create 
         */
        _getDirectory(path = '', create = false) {
            return toPromise(this._root.getDirectory, this._root, path, { create, exclusive: false })
        }
    
        async _readEntriesRecursively(rootEntry, refResults) {
    
            if (rootEntry.isFile) {
                return Promise.resolve(rootEntry)
            }
            let reader = rootEntry.createReader()
            let entries = await toPromise(reader.readEntries, reader)
            refResults.push(...entries)
            let psEntries = entries.map(entry => this._readEntriesRecursively(entry, refResults))
            return Promise.all(psEntries)
        }
    
        /**
         * 获得Entry
         * @param {*路径} path 
         */
        resolveLocalFileSystemURL(path) {
            return toPromise(window.resolveLocalFileSystemURL, window, `${this._fsBaseUrl}${path.startsWith('/') ? path.substr(1) : path}`)
        }
    
        /**
         * 获得文件
         * @param {*文件路径} path 
         */
        async getFile(path) {
            let fe = await this._getFileEntry(path)
            return toPromise(fe.file, fe)
        }
    
        /**
         * 往文件写入内容
         * @param {*文件路径} path 
         * @param {*写入的内容} content 
         * @param {*数据类型} type 
         * @param {*是否是append} append 
         */
        async writeToFile(path, content, type = 'text/plain', append = false) {
    
            let fe = await this._getFileEntry(path, true)
            let writer = await toPromise(fe.createWriter, fe);
            let data = content;
    
            //不是blob,转为blob
            if (content instanceof ArrayBuffer) {
                data = new Blob([new Uint8Array(content)], { type })
            } else if (typeof content == 'string') {
                data = new Blob([content], { type: 'text/plain' })
            } else {
                data = new Blob([content])
            }
    
            if (append) {
                writer.seek(writer.length)
            }
    
            return new Promise((resolve, reject) => {
                //写入成功
                writer.onwriteend = () => {
                    resolve(true)
                }
    
                //写入失败
                writer.onerror = (err) => {
                    reject(err)
                }
    
                writer.write(data)
            })
        }
    
        /**
         * 获取指定目录下的文件和文件夹
         * @param {*路径} path 
         */
        async readEntries(path = '') {
            let entry = null
            if (!path) {
                entry = this._root
            } else {
                entry = await this.resolveLocalFileSystemURL(path)
            }
            let reader = entry.createReader()
            return toPromise(reader.readEntries, reader);
        }
    
        /**
         * 获取所有的文件和文件夹,按照路径排序
         */
        async readAllEntries() {
            let refResults = []
            let entries = await this._readEntriesRecursively(this._root, refResults)
            refResults.sort((a, b) => a.fullPath > b.fullPath)
            return refResults
    
        }
    
    
        /**
         * 确认目录存在,递归检查,没有会自动创建
         * @param {*} directory 
         */
        async ensureDirectory(directory = '') {
            //过滤空的目录,比如 '/music/' => ['','music','']
            let _dirs = directory.split('/').filter(v => !!v)
    
            if (!_dirs || _dirs.length == 0) {
                return Promise.resolve(true)
            }
    
            return promiseForEach(_dirs, (dir, index) => {
                return this._getDirectory(_dirs.slice(0, index + 1).join('/'), true)
            }, true).then((rs) => {
                console.log(rs)
                return true
            })
        }
    
    
    
        /**
         * 清除所有的文件和文件夹
         */
        async clear() {
            let entries = await this.readEntries()
            let ps_entries = entries.map(e => e.isFile ? toPromise(e.remove, e) : toPromise(e.removeRecursively, e))
            return Promise.all(ps_entries)
        }
    
        /**
        * Promise里面的错误处理
        * @param {*reject}  
        */
        errorHandler(reject) {
            return (error) => {
                reject(error)
            }
        }
    
    }
    
    
    // 测试语句
    //读取某个目录的子目录和文件:  LocalFileSystem.getInstance().then(fs=>fs.readEntries()).then(f=>console.log(f))
    //写文件         LocalFileSystem.getInstance().then(fs=>fs.writeToFile('music/txt.txt','爱死你')).then(f=>console.log(f))
    //获取文件:     LocalFileSystem.getInstance().then(fs=>fs.getFile('music/txt.txt')).then(f=>console.log(f))
    //递归创建目录:  LocalFileSystem.getInstance().then(fs=>fs.ensureDirectory('music/vbox')).then(r=>console.log('r:' + r))
    //递归获取:     LocalFileSystem.getInstance().then(fs=>fs.readAllEntries()).then(f=>console.log(f))
    //删除所有:     LocalFileSystem.getInstance().then(fs=>fs.clear()).then(f=>console.log(f)).catch(err=>console.log(err)) 
    

      

    当然测试语句也在上面了,因为用了 await,那么大家自然知道了。需要 chrome://flags开启javascript的特性。

    如果你有兴趣,代码地址:https://github.com/xiangwenhu/BlogCodes/tree/master/client/FileSystem,下载下来

    npm install 之后, node server/app.js就可以查询demo了

     
     
    参考:
  • 相关阅读:
    NSURLSession的文件下载
    JSON解析(序列化和反序列化)
    NSURLSession的知识小记
    RunLoop的知识小记
    NSCach 的知识小记
    多图片下载综合案例-磁盘缓存处理
    模仿UIApplication创建单例
    LayoutSubviews的调用
    setValueForKeysWithDictionary的底层实现
    剑指offer 20:顺时针打印矩阵
  • 原文地址:https://www.cnblogs.com/cloud-/p/6807643.html
Copyright © 2011-2022 走看看