image cache provider

'use strict';

const _ = require('lodash');
const RNFS = require('react-native-fs');

const {
    DocumentDirectoryPath
} = RNFS;

const SHA1 = require("crypto-js/sha1");
const URL = require('url-parse');

const defaultOptions = {
    useQueryParamsInCacheKey: false
};

const activeDownloads = {};

const BaseDirPath = 'baseDirPath';

function getBasePath(){
  return DocumentDirectoryPath + '/' + BaseDirPath;
}

function serializeObjectKeys(obj) {
    return _(obj)
        .toPairs()
        .sortBy(a => a[0])
        .map(a => a[1])
        .value();
}

function getQueryForCacheKey(url, useQueryParamsInCacheKey) {
    if (_.isArray(useQueryParamsInCacheKey)) {
        return serializeObjectKeys(_.pick(url.query, useQueryParamsInCacheKey));
    }
    if (useQueryParamsInCacheKey) {
        return serializeObjectKeys(url.query);
    }
    return '';
}

function generateCacheKey(url, options) {
    const parsedUrl = new URL(url, null, true);

    const pathParts = parsedUrl.pathname.split('/');

    // last path part is the file name
    const fileName = pathParts.pop();
    const filePath = pathParts.join('/');

    const parts = fileName.split('.');
    // TODO - try to figure out the file type or let the user provide it, for now use jpg as default
    const type = parts.length > 1 ? parts.pop() : 'jpg';

    const cacheable = filePath + fileName + type + getQueryForCacheKey(parsedUrl, options.useQueryParamsInCacheKey);
    return SHA1(cacheable) + '.' + type;
}

function getCachePath(url, options) {
    if (options.cacheGroup) {
        return options.cacheGroup;
    }
    const parsedUrl = new URL(url);
    return parsedUrl.host;
}

function getCachedImageFilePath(url, options) {
    const cacheKey = generateCacheKey(url, options);
    const cachePath = getCachePath(url, options);

    const dirPath = getBasePath() + '/' + cachePath;
    return dirPath + '/' + cacheKey;
}

function deleteFile(filePath) {
    return RNFS.exists(filePath)
        .then(res => res && RNFS.unlink(filePath))
        .catch((err) => {
            // swallow error to always resolve
        });
}

function ensurePath(filePath) {
    const parts = filePath.split('/');
    const dirPath = _.initial(parts).join('/');
    return RNFS.mkdir(dirPath, {NSURLIsExcludedFromBackupKey: true});
}

/**
 * returns a promise that is resolved when the download of the requested file
 * is complete and the file is saved.
 * if the download fails, or was stopped the partial file is deleted, and the
 * promise is rejected
 * @param fromUrl
 * @param toFile
 * @returns {Promise}
 */
function downloadImage(fromUrl, toFile) {
    // use toFile as the key as is was created using the cacheKey
    if (!_.has(activeDownloads, toFile)) {
        // create an active download for this file
        activeDownloads[toFile] = new Promise((resolve, reject) => {
            const downloadOptions = {
                fromUrl,
                toFile
            };
            RNFS.downloadFile(downloadOptions).promise
                .then(res => {
                    if (Math.floor(res.statusCode / 100) == 2) {
                      resolve(toFile);
                    } else {
                      return Promise.reject('Failed to successfully download image')
                    }
                })
                .catch(err => {
                    return deleteFile(toFile)
                        .then(() => reject(err))
                })
                .finally(() => {
                    // cleanup
                    delete activeDownloads[toFile];
                });
        });
    }
    return activeDownloads[toFile];
}

function createPrefetcer(list) {
    const urls = _.clone(list);
    return {
        next() {
            return urls.shift();
        }
    };
}

function runPrefetchTask(prefetcher, options) {
    const url = prefetcher.next();
    if (!url) {
        return Promise.resolve();
    }
    // if url is cacheable - cache it
    if (isCacheable(url)) {
        // check cache
        return getCachedImagePath(url, options)
        // if not found download
            .catch(() => cacheImage(url, options))
            // then run next task
            .then(() => runPrefetchTask(prefetcher, options));
    }
    // else get next
    return runPrefetchTask(prefetcher, options);
}

// API

function isCacheable(url) {
    return _.isString(url) && (_.startsWith(url, 'http://') || _.startsWith(url, 'https://'));
}

function getCachedImagePath(url, options = defaultOptions) {
    const filePath = getCachedImageFilePath(url, options);
    return RNFS.stat(filePath)
        .then(res => {
            if (!res.isFile()) {
                // reject the promise if res is not a file
                throw new Error('Failed to get image from cache');
            }
            if (!res.size) {
                // something went wrong with the download, file size is 0, remove it
                return deleteFile(filePath)
                    .then(() => {
                        throw new Error('Failed to get image from cache');
                    });
            }
            return filePath;
        })
        .catch(err => {
            throw err;
        })
}

function cacheImage(url, options = defaultOptions) {
    const filePath = getCachedImageFilePath(url, options);
    return ensurePath(filePath)
        .then(() => downloadImage(url, filePath));
}

function deleteCachedImage(url, options = defaultOptions) {
    const filePath = getCachedImageFilePath(url, options);
    return deleteFile(filePath);
}

function cacheMultipleImages(urls, options = defaultOptions) {
    const prefetcher = createPrefetcer(urls);
    const numberOfWorkers = urls.length;
    const promises = _.times(numberOfWorkers, () =>
        runPrefetchTask(prefetcher, options)
    );
    return Promise.all(promises);
}

function deleteMultipleCachedImages(urls, options = defaultOptions) {
    return _.reduce(urls, (p, url) =>
            p.then(() => deleteCachedImage(url, options)),
        Promise.resolve()
    );
}

function clearCache() {
    deleteFile(getBasePath());
    ensurePath(getBasePath());
}

function getStat(path) {
  return RNFS.stat(path)
    .then((res) => {
      if (res) {
        return {
          ...res,
          path: path
        };
      }
      return null;
    }, (error) => {
      return 0;
    })
}

function getDirSize(path) {
  return new Promise((resolve, reject) => {
    let total = 0;
    RNFS.readdir(path)
      .then((subItems) => {
        const statArr = [];
        const funcArr = [];
        if (!subItems || subItems.length <= 0) {
          resolve(0);
          return;
        }
        subItems.forEach((itemPath) => {
          statArr.push(getStat(path + '/' + itemPath))
        });
        Promise.all(statArr)
          .then((arr) => {
            if (!arr || arr.length <= 0) {
              resolve(0);
              return;
            }
            arr.forEach((item) => {
              if (item) {
                if (item.isFile()) {
                  total += item.size;
                } else {
                  funcArr.push(getDirSize(item.path));
                }
              }
            });
            if (funcArr.length > 0) {
              Promise.all(funcArr)
                .then((sizeList) => {
                  total += sizeList.reduce((a, b) => {
                    return a + b;
                  });
                  resolve(total);
                });
            } else {
              resolve(total);
            }
          });
      }, (error) => {
        resolve(0);
      });
  });
}

function getCacheSize() {
  return getDirSize(getBasePath());
}

module.exports = {
    isCacheable,
    getCachedImagePath,
    cacheImage,
    deleteCachedImage,
    cacheMultipleImages,
    deleteMultipleCachedImages,
    clearCache,
    getCacheSize
};

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末侨舆,一起剝皮案震驚了整個(gè)濱河市霸旗,隨后出現(xiàn)的幾起案子逮栅,更是在濱河造成了極大的恐慌,老刑警劉巖缔俄,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疗绣,死亡現(xiàn)場(chǎng)離奇詭異线召,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)多矮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門缓淹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人塔逃,你說我怎么就攤上這事讯壶。” “怎么了湾盗?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵伏蚊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我格粪,道長(zhǎng)躏吊,這世上最難降的妖魔是什么氛改? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮比伏,結(jié)果婚禮上胜卤,老公的妹妹穿的比我還像新娘。我一直安慰自己凳怨,他們只是感情好瑰艘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肤舞,像睡著了一般紫新。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上李剖,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天芒率,我揣著相機(jī)與錄音,去河邊找鬼篙顺。 笑死偶芍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的德玫。 我是一名探鬼主播匪蟀,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼宰僧!你這毒婦竟也來了材彪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤琴儿,失蹤者是張志新(化名)和其女友劉穎段化,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體造成,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡显熏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晒屎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喘蟆。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鼓鲁,靈堂內(nèi)的尸體忽然破棺而出蕴轨,到底是詐尸還是另有隱情,我是刑警寧澤坐桩,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布尺棋,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏膘螟。R本人自食惡果不足惜成福,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荆残。 院中可真熱鬧奴艾,春花似錦、人聲如沸内斯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)俘闯。三九已至潭苞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間真朗,已是汗流浹背此疹。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遮婶,地道東北人蝗碎。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像旗扑,于是被迫代替她去往敵國(guó)和親蹦骑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 介紹 OpenStack使用分布式存儲(chǔ)Ceph后臀防,就不需要Glance的cache image了眠菇,我們可以通過修改...
    chendihao閱讀 1,376評(píng)論 0 0
  • 介紹 OpenStack使用Ceph分布式存儲(chǔ)后,理論上不需要cache image了清钥,在部署時(shí)可以修改這個(gè)配置琼锋,...
    chendihao閱讀 1,277評(píng)論 0 0
  • 標(biāo)簽: cinder openstack 翻譯 原文: Image-Volume cache Openstack塊...
    cheneydc閱讀 2,594評(píng)論 0 2
  • 珞珈三杰的悲喜人生 文/丁茉莉 武...
    丁茉莉閱讀 1,837評(píng)論 0 0
  • 里約起蒼黃放闺, 奧運(yùn)大戰(zhàn)忙祟昭, 女排奪冠尤可嘉, 無敵靠自強(qiáng)怖侦。 郎導(dǎo)傳精神篡悟, 布陣調(diào)度詳, 長(zhǎng)傳短吊攔扣狠匾寝, 個(gè)個(gè)賽虎...
    小肉巴閱讀 401評(píng)論 2 4