123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- /**
- * LRU 文件存储,使用该 downloader 可以让下载的文件存储在本地,下次进入小程序后可以直接使用
- * 详细设计文档可查看 https://juejin.im/post/5b42d3ede51d4519277b6ce3
- */
- const util = require('./util');
- const sha1 = require('./sha1');
- const SAVED_FILES_KEY = 'savedFiles';
- const KEY_TOTAL_SIZE = 'totalSize';
- const KEY_PATH = 'path';
- const KEY_TIME = 'time';
- const KEY_SIZE = 'size'; // 可存储总共为 6M,目前小程序可允许的最大本地存储为 10M
- let MAX_SPACE_IN_B = 6 * 1024 * 1024;
- let savedFiles = {};
- export default class Dowloader {
- constructor() {
- // app 如果设置了最大存储空间,则使用 app 中的
- if (getApp().globalData.PAINTER_MAX_LRU_SPACE) {
- MAX_SPACE_IN_B = getApp().globalData.PAINTER_MAX_LRU_SPACE;
- }
- uni.getStorage({
- key: SAVED_FILES_KEY,
- success: function (res) {
- if (res.data) {
- savedFiles = res.data;
- }
- }
- });
- }
- /**
- * 下载文件,会用 lru 方式来缓存文件到本地
- * @param {String} url 文件的 url
- */
- download(url, lru) {
- return new Promise((resolve, reject) => {
- if (!(url && util.isValidUrl(url))) {
- resolve(url);
- return;
- }
- const fileName = getFileName(url);
- if (!lru) {
- // 无 lru 情况下直接判断 临时文件是否存在,不存在重新下载
- uni.getFileInfo({
- filePath: fileName,
- success: () => {
- resolve(url);
- },
- fail: () => {
- if (util.isOnlineUrl(url)) {
- downloadFile(url, lru).then(
- (path) => {
- resolve(path);
- },
- () => {
- reject();
- }
- );
- } else if (util.isDataUrl(url)) {
- transformBase64File(url, lru).then(
- (path) => {
- resolve(path);
- },
- () => {
- reject();
- }
- );
- }
- }
- });
- return;
- }
- const file = getFile(fileName);
- if (file) {
- if (file[KEY_PATH].indexOf('//usr/') !== -1) {
- uni.getFileInfo({
- filePath: file[KEY_PATH],
- success() {
- resolve(file[KEY_PATH]);
- },
- fail(error) {
- console.error(`base64 file broken, ${JSON.stringify(error)}`);
- transformBase64File(url, lru).then(
- (path) => {
- resolve(path);
- },
- () => {
- reject();
- }
- );
- }
- });
- } else {
- // 检查文件是否正常,不正常需要重新下载
- uni.getSavedFileInfo({
- filePath: file[KEY_PATH],
- success: (res) => {
- resolve(file[KEY_PATH]);
- },
- fail: (error) => {
- console.error(`the file is broken, redownload it, ${JSON.stringify(error)}`);
- downloadFile(url, lru).then(
- (path) => {
- resolve(path);
- },
- () => {
- reject();
- }
- );
- }
- });
- }
- } else {
- if (util.isOnlineUrl(url)) {
- downloadFile(url, lru).then(
- (path) => {
- resolve(path);
- },
- () => {
- reject();
- }
- );
- } else if (util.isDataUrl(url)) {
- transformBase64File(url, lru).then(
- (path) => {
- resolve(path);
- },
- () => {
- reject();
- }
- );
- }
- }
- });
- }
- }
- function getFileName(url) {
- if (util.isDataUrl(url)) {
- const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(url) || [];
- const fileName = `${sha1.hex_sha1(bodyData)}.${format}`;
- return fileName;
- } else {
- return url;
- }
- }
- function transformBase64File(base64data, lru) {
- return new Promise((resolve, reject) => {
- const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || [];
- if (!format) {
- console.error('base parse failed');
- reject();
- return;
- }
- const fileName = `${sha1.hex_sha1(bodyData)}.${format}`;
- const path = `${uni.env.USER_DATA_PATH}/${fileName}`;
- const buffer = uni.base64ToArrayBuffer(bodyData.replace(/[\r\n]/g, ''));
- uni.getFileSystemManager().writeFile({
- filePath: path,
- data: buffer,
- encoding: 'binary',
- success() {
- uni.getFileInfo({
- filePath: path,
- success: (tmpRes) => {
- const newFileSize = tmpRes.size;
- lru
- ? doLru(newFileSize).then(
- () => {
- saveFile(fileName, newFileSize, path, true).then((filePath) => {
- resolve(filePath);
- });
- },
- () => {
- resolve(path);
- }
- )
- : resolve(path);
- },
- fail: (error) => {
- // 文件大小信息获取失败,则此文件也不要进行存储
- console.error(`getFileInfo ${path} failed, ${JSON.stringify(error)}`);
- resolve(path);
- }
- });
- },
- fail(err) {
- console.log(err);
- }
- });
- });
- }
- function downloadFile(url, lru) {
- return new Promise((resolve, reject) => {
- uni.downloadFile({
- url: url,
- success: function (res) {
- if (res.statusCode !== 200) {
- console.error(`downloadFile ${url} failed res.statusCode is not 200`);
- reject();
- return;
- }
- const { tempFilePath } = res;
- uni.getFileInfo({
- filePath: tempFilePath,
- success: (tmpRes) => {
- const newFileSize = tmpRes.size;
- lru
- ? doLru(newFileSize).then(
- () => {
- saveFile(url, newFileSize, tempFilePath).then((filePath) => {
- resolve(filePath);
- });
- },
- () => {
- resolve(tempFilePath);
- }
- )
- : resolve(tempFilePath);
- },
- fail: (error) => {
- // 文件大小信息获取失败,则此文件也不要进行存储
- console.error(`getFileInfo ${res.tempFilePath} failed, ${JSON.stringify(error)}`);
- resolve(res.tempFilePath);
- }
- });
- },
- fail: function (error) {
- console.error(`downloadFile failed, ${JSON.stringify(error)} `);
- reject();
- }
- });
- });
- }
- function saveFile(key, newFileSize, tempFilePath, isDataUrl = false) {
- return new Promise((resolve, reject) => {
- if (isDataUrl) {
- const totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0;
- savedFiles[key] = {};
- savedFiles[key][KEY_PATH] = tempFilePath;
- savedFiles[key][KEY_TIME] = new Date().getTime();
- savedFiles[key][KEY_SIZE] = newFileSize;
- savedFiles['totalSize'] = newFileSize + totalSize;
- uni.setStorage({
- key: SAVED_FILES_KEY,
- data: savedFiles
- });
- resolve(tempFilePath);
- return;
- }
- uni.saveFile({
- tempFilePath: tempFilePath,
- success: (fileRes) => {
- const totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0;
- savedFiles[key] = {};
- savedFiles[key][KEY_PATH] = fileRes.savedFilePath;
- savedFiles[key][KEY_TIME] = new Date().getTime();
- savedFiles[key][KEY_SIZE] = newFileSize;
- savedFiles['totalSize'] = newFileSize + totalSize;
- uni.setStorage({
- key: SAVED_FILES_KEY,
- data: savedFiles
- });
- resolve(fileRes.savedFilePath);
- },
- fail: (error) => {
- console.error(`saveFile ${key} failed, then we delete all files, ${JSON.stringify(error)}`); // 由于 saveFile 成功后,res.tempFilePath 处的文件会被移除,所以在存储未成功时,我们还是继续使用临时文件
- resolve(tempFilePath); // 如果出现错误,就直接情况本地的所有文件,因为你不知道是不是因为哪次lru的某个文件未删除成功
- reset();
- }
- });
- });
- }
- /**
- * 清空所有下载相关内容
- */
- function reset() {
- uni.removeStorage({
- key: SAVED_FILES_KEY,
- success: () => {
- uni.getSavedFileList({
- success: (listRes) => {
- removeFiles(listRes.fileList);
- },
- fail: (getError) => {
- console.error(`getSavedFileList failed, ${JSON.stringify(getError)}`);
- }
- });
- }
- });
- }
- function doLru(size) {
- if (size > MAX_SPACE_IN_B) {
- return Promise.reject();
- }
- return new Promise((resolve, reject) => {
- let totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0;
- if (size + totalSize <= MAX_SPACE_IN_B) {
- resolve();
- return;
- } // 如果加上新文件后大小超过最大限制,则进行 lru
- const pathsShouldDelete = []; // 按照最后一次的访问时间,从小到大排序
- const allFiles = JSON.parse(JSON.stringify(savedFiles));
- delete allFiles[KEY_TOTAL_SIZE];
- const sortedKeys = Object.keys(allFiles).sort((a, b) => {
- return allFiles[a][KEY_TIME] - allFiles[b][KEY_TIME];
- });
- for (const sortedKey of sortedKeys) {
- totalSize -= savedFiles[sortedKey].size;
- pathsShouldDelete.push(savedFiles[sortedKey][KEY_PATH]);
- delete savedFiles[sortedKey];
- if (totalSize + size < MAX_SPACE_IN_B) {
- break;
- }
- }
- savedFiles['totalSize'] = totalSize;
- uni.setStorage({
- key: SAVED_FILES_KEY,
- data: savedFiles,
- success: () => {
- // 保证 storage 中不会存在不存在的文件数据
- if (pathsShouldDelete.length > 0) {
- removeFiles(pathsShouldDelete);
- }
- resolve();
- },
- fail: (error) => {
- console.error(`doLru setStorage failed, ${JSON.stringify(error)}`);
- reject();
- }
- });
- });
- }
- function removeFiles(pathsShouldDelete) {
- for (const pathDel of pathsShouldDelete) {
- let delPath = pathDel;
- if (typeof pathDel === 'object') {
- delPath = pathDel.filePath;
- }
- if (delPath.indexOf('//usr/') !== -1) {
- uni.getFileSystemManager().unlink({
- filePath: delPath,
- fail(error) {
- console.error(`removeSavedFile ${pathDel} failed, ${JSON.stringify(error)}`);
- }
- });
- } else {
- uni.removeSavedFile({
- filePath: delPath,
- fail: (error) => {
- console.error(`removeSavedFile ${pathDel} failed, ${JSON.stringify(error)}`);
- }
- });
- }
- }
- }
- function getFile(key) {
- if (!savedFiles[key]) {
- return;
- }
- savedFiles[key]['time'] = new Date().getTime();
- uni.setStorage({
- key: SAVED_FILES_KEY,
- data: savedFiles
- });
- return savedFiles[key];
- }
|