mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Extracted local storage adapters' into a base class
refs https://github.com/TryGhost/Toolbox/issues/95 - The MediaStorage adapter and LocalFileStorage were almost identical, having a common base class makes sense here. - Having a distinct class for the "LocalFileStorage" makes it easy to spot the implementation difference from the StorageBase - the "saveRaw" method, which is not present in the StorageBase - The LocalFileStorage will become an LocalImageStorage in next commit as that name corresponds way better to what it does! - Test files need a good cleanup
This commit is contained in:
parent
ed4586c28c
commit
f3fc1bd5d4
3 changed files with 210 additions and 326 deletions
|
@ -1,30 +1,26 @@
|
||||||
// # Local File System Image Storage module
|
// # Local File System Image Storage module
|
||||||
// The (default) module for storing images, using the local file system
|
// The (default) module for storing images, using the local file system
|
||||||
const serveStatic = require('../../../shared/express').static;
|
|
||||||
|
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const Promise = require('bluebird');
|
|
||||||
const moment = require('moment');
|
|
||||||
const config = require('../../../shared/config');
|
const config = require('../../../shared/config');
|
||||||
const tpl = require('@tryghost/tpl');
|
|
||||||
const logging = require('@tryghost/logging');
|
|
||||||
const errors = require('@tryghost/errors');
|
|
||||||
const constants = require('@tryghost/constants');
|
|
||||||
const urlUtils = require('../../../shared/url-utils');
|
|
||||||
const StorageBase = require('ghost-storage-base');
|
|
||||||
|
|
||||||
const messages = {
|
const urlUtils = require('../../../shared/url-utils');
|
||||||
imageNotFound: 'Image not found',
|
const LocalStorageBase = require('./LocalStorageBase');
|
||||||
imageNotFoundWithRef: 'Image not found: {img}',
|
|
||||||
cannotReadImage: 'Could not read image: {img}'
|
let messages = {
|
||||||
|
notFound: 'Image not found',
|
||||||
|
notFoundWithRef: 'Image not found: {file}',
|
||||||
|
cannotRead: 'Could not read image: {file}'
|
||||||
};
|
};
|
||||||
|
|
||||||
class LocalFileStore extends StorageBase {
|
class LocalImagesStore extends LocalStorageBase {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super({
|
||||||
|
storagePath: config.getContentPath('images'),
|
||||||
this.storagePath = config.getContentPath('images');
|
staticFileURLPrefix: urlUtils.STATIC_IMAGE_URL_PREFIX,
|
||||||
|
errorMessages: messages
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,156 +39,12 @@ class LocalFileStore extends StorageBase {
|
||||||
// For local file system storage can use relative path so add a slash
|
// For local file system storage can use relative path so add a slash
|
||||||
const fullUrl = (
|
const fullUrl = (
|
||||||
urlUtils.urlJoin('/', urlUtils.getSubdir(),
|
urlUtils.urlJoin('/', urlUtils.getSubdir(),
|
||||||
urlUtils.STATIC_IMAGE_URL_PREFIX,
|
this.staticFileURLPrefix,
|
||||||
targetPath)
|
targetPath)
|
||||||
).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
|
).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
|
||||||
|
|
||||||
return fullUrl;
|
return fullUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the image to storage (the file system)
|
|
||||||
* - image is the express image object
|
|
||||||
* - returns a promise which ultimately returns the full url to the uploaded image
|
|
||||||
*
|
|
||||||
* @param {StorageBase.Image} image
|
|
||||||
* @param {String} targetDir
|
|
||||||
* @returns {Promise<String>}
|
|
||||||
*/
|
|
||||||
async save(image, targetDir) {
|
|
||||||
let targetFilename;
|
|
||||||
|
|
||||||
// NOTE: the base implementation of `getTargetDir` returns the format this.storagePath/YYYY/MM
|
|
||||||
targetDir = targetDir || this.getTargetDir(this.storagePath);
|
|
||||||
|
|
||||||
const filename = await this.getUniqueFileName(image, targetDir);
|
|
||||||
|
|
||||||
targetFilename = filename;
|
|
||||||
await fs.mkdirs(targetDir);
|
|
||||||
|
|
||||||
await fs.copy(image.path, targetFilename);
|
|
||||||
|
|
||||||
// The src for the image must be in URI format, not a file system path, which in Windows uses \
|
|
||||||
// For local file system storage can use relative path so add a slash
|
|
||||||
const fullUrl = (
|
|
||||||
urlUtils.urlJoin('/', urlUtils.getSubdir(),
|
|
||||||
urlUtils.STATIC_IMAGE_URL_PREFIX,
|
|
||||||
path.relative(this.storagePath, targetFilename))
|
|
||||||
).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
|
|
||||||
|
|
||||||
return fullUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
exists(fileName, targetDir) {
|
|
||||||
const filePath = path.join(targetDir || this.storagePath, fileName);
|
|
||||||
|
|
||||||
return fs.stat(filePath)
|
|
||||||
.then(() => {
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For some reason send divides the max age number by 1000
|
|
||||||
* Fallthrough: false ensures that if an image isn't found, it automatically 404s
|
|
||||||
* Wrap server static errors
|
|
||||||
*
|
|
||||||
* @returns {serveStaticContent}
|
|
||||||
*/
|
|
||||||
serve() {
|
|
||||||
const {storagePath} = this;
|
|
||||||
|
|
||||||
return function serveStaticContent(req, res, next) {
|
|
||||||
const startedAtMoment = moment();
|
|
||||||
|
|
||||||
return serveStatic(
|
|
||||||
storagePath,
|
|
||||||
{
|
|
||||||
maxAge: constants.ONE_YEAR_MS,
|
|
||||||
fallthrough: false,
|
|
||||||
onEnd: () => {
|
|
||||||
logging.info('LocalFileStorage.serve', req.path, moment().diff(startedAtMoment, 'ms') + 'ms');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)(req, res, (err) => {
|
|
||||||
if (err) {
|
|
||||||
if (err.statusCode === 404) {
|
|
||||||
return next(new errors.NotFoundError({
|
|
||||||
message: tpl(messages.imageNotFound),
|
|
||||||
code: 'STATIC_FILE_NOT_FOUND',
|
|
||||||
property: err.path
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.statusCode === 400) {
|
|
||||||
return next(new errors.BadRequestError({err: err}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.statusCode === 403) {
|
|
||||||
return next(new errors.NoPermissionError({err: err}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(new errors.GhostError({err: err}));
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Not implemented.
|
|
||||||
* @returns {Promise.<*>}
|
|
||||||
*/
|
|
||||||
delete() {
|
|
||||||
return Promise.reject('not implemented');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads bytes from disk for a target image
|
|
||||||
* - path of target image (without content path!)
|
|
||||||
*
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
read(options) {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
// remove trailing slashes
|
|
||||||
options.path = (options.path || '').replace(/\/$|\\$/, '');
|
|
||||||
|
|
||||||
const targetPath = path.join(this.storagePath, options.path);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fs.readFile(targetPath, (err, bytes) => {
|
|
||||||
if (err) {
|
|
||||||
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
|
|
||||||
return reject(new errors.NotFoundError({
|
|
||||||
err: err,
|
|
||||||
message: tpl(messages.imageNotFoundWithRef, {img: options.path})
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.code === 'ENAMETOOLONG') {
|
|
||||||
return reject(new errors.BadRequestError({err: err}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.code === 'EACCES') {
|
|
||||||
return reject(new errors.NoPermissionError({err: err}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return reject(new errors.GhostError({
|
|
||||||
err: err,
|
|
||||||
message: tpl(messages.cannotReadImage, {img: options.path})
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(bytes);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = LocalFileStore;
|
module.exports = LocalImagesStore;
|
||||||
|
|
|
@ -1,175 +1,21 @@
|
||||||
// # Local File System Video Storage module
|
// # Local File System Video Storage module
|
||||||
// The (default) module for storing media, using the local file system
|
// The (default) module for storing media, using the local file system
|
||||||
const serveStatic = require('../../../shared/express').static;
|
|
||||||
|
|
||||||
const fs = require('fs-extra');
|
|
||||||
const path = require('path');
|
|
||||||
const Promise = require('bluebird');
|
|
||||||
const moment = require('moment');
|
|
||||||
const config = require('../../../shared/config');
|
const config = require('../../../shared/config');
|
||||||
const tpl = require('@tryghost/tpl');
|
|
||||||
const logging = require('@tryghost/logging');
|
|
||||||
const errors = require('@tryghost/errors');
|
|
||||||
const constants = require('@tryghost/constants');
|
const constants = require('@tryghost/constants');
|
||||||
const urlUtils = require('../../../shared/url-utils');
|
const LocalStorageBase = require('./LocalStorageBase');
|
||||||
const StorageBase = require('ghost-storage-base');
|
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
videoNotFound: 'Video not found',
|
notFound: 'Media file not found',
|
||||||
videoNotFoundWithRef: 'Video not found: {video}',
|
notFoundWithRef: 'Media file not found: {file}',
|
||||||
cannotReadVideo: 'Could not read video: {video}'
|
cannotRead: 'Could not read media file: {file}'
|
||||||
};
|
};
|
||||||
|
|
||||||
class LocalMediaStore extends StorageBase {
|
class LocalMediaStore extends LocalStorageBase {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super({
|
||||||
|
storagePath: config.getContentPath('media'),
|
||||||
this.storagePath = config.getContentPath('media');
|
staticFileURLPrefix: constants.STATIC_MEDIA_URL_PREFIX,
|
||||||
}
|
errorMessages: messages
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the video to storage (the file system)
|
|
||||||
*
|
|
||||||
* @param {Object} video
|
|
||||||
* @param {String} video.name
|
|
||||||
* @param {String} video.type
|
|
||||||
* @param {String} video.path
|
|
||||||
* @param {String} targetDir
|
|
||||||
* @returns {Promise<String>}
|
|
||||||
*/
|
|
||||||
async save(video, targetDir) {
|
|
||||||
let targetFilename;
|
|
||||||
|
|
||||||
// NOTE: the base implementation of `getTargetDir` returns the format this.storagePath/YYYY/MM
|
|
||||||
targetDir = targetDir || this.getTargetDir(this.storagePath);
|
|
||||||
|
|
||||||
const filename = await this.getUniqueFileName(video, targetDir);
|
|
||||||
|
|
||||||
targetFilename = filename;
|
|
||||||
await fs.mkdirs(targetDir);
|
|
||||||
|
|
||||||
await fs.copy(video.path, targetFilename);
|
|
||||||
|
|
||||||
// The src for the video must be in URI format, not a file system path, which in Windows uses \
|
|
||||||
// For local file system storage can use relative path so add a slash
|
|
||||||
const fullUrl = (
|
|
||||||
urlUtils.urlJoin('/',
|
|
||||||
urlUtils.getSubdir(),
|
|
||||||
constants.STATIC_MEDIA_URL_PREFIX,
|
|
||||||
path.relative(this.storagePath, targetFilename))
|
|
||||||
).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
|
|
||||||
|
|
||||||
return fullUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
exists(fileName, targetDir) {
|
|
||||||
const filePath = path.join(targetDir || this.storagePath, fileName);
|
|
||||||
|
|
||||||
return fs.stat(filePath)
|
|
||||||
.then(() => {
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For some reason send divides the max age number by 1000
|
|
||||||
* Fallthrough: false ensures that if an video isn't found, it automatically 404s
|
|
||||||
* Wrap server static errors
|
|
||||||
*
|
|
||||||
* @returns {serveStaticContent}
|
|
||||||
*/
|
|
||||||
serve() {
|
|
||||||
const {storagePath} = this;
|
|
||||||
|
|
||||||
return function serveStaticContent(req, res, next) {
|
|
||||||
const startedAtMoment = moment();
|
|
||||||
|
|
||||||
return serveStatic(
|
|
||||||
storagePath,
|
|
||||||
{
|
|
||||||
maxAge: constants.ONE_YEAR_MS,
|
|
||||||
fallthrough: false,
|
|
||||||
onEnd: () => {
|
|
||||||
logging.info('LocalMediaStorage.serve', req.path, moment().diff(startedAtMoment, 'ms') + 'ms');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)(req, res, (err) => {
|
|
||||||
if (err) {
|
|
||||||
if (err.statusCode === 404) {
|
|
||||||
return next(new errors.NotFoundError({
|
|
||||||
message: tpl(messages.videoNotFound),
|
|
||||||
code: 'STATIC_FILE_NOT_FOUND',
|
|
||||||
property: err.path
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.statusCode === 400) {
|
|
||||||
return next(new errors.BadRequestError({err: err}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.statusCode === 403) {
|
|
||||||
return next(new errors.NoPermissionError({err: err}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(new errors.GhostError({err: err}));
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Not implemented.
|
|
||||||
* @returns {Promise.<*>}
|
|
||||||
*/
|
|
||||||
delete() {
|
|
||||||
return Promise.reject('not implemented');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads bytes from disk for a target video
|
|
||||||
* - path of target video (without content path!)
|
|
||||||
*
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
read(options) {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
// remove trailing slashes
|
|
||||||
options.path = (options.path || '').replace(/\/$|\\$/, '');
|
|
||||||
|
|
||||||
const targetPath = path.join(this.storagePath, options.path);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fs.readFile(targetPath, (err, bytes) => {
|
|
||||||
if (err) {
|
|
||||||
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
|
|
||||||
return reject(new errors.NotFoundError({
|
|
||||||
err: err,
|
|
||||||
message: tpl(messages.videoNotFoundWithRef, {video: options.path})
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.code === 'ENAMETOOLONG') {
|
|
||||||
return reject(new errors.BadRequestError({err: err}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.code === 'EACCES') {
|
|
||||||
return reject(new errors.NoPermissionError({err: err}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return reject(new errors.GhostError({
|
|
||||||
err: err,
|
|
||||||
message: tpl(messages.cannotReadVideo, {video: options.path})
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(bytes);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
186
core/server/adapters/storage/LocalStorageBase.js
Normal file
186
core/server/adapters/storage/LocalStorageBase.js
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
// # Local File Base Storage module
|
||||||
|
// The (default) module for storing files using the local file system
|
||||||
|
const serveStatic = require('../../../shared/express').static;
|
||||||
|
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
const Promise = require('bluebird');
|
||||||
|
const moment = require('moment');
|
||||||
|
const tpl = require('@tryghost/tpl');
|
||||||
|
const logging = require('@tryghost/logging');
|
||||||
|
const errors = require('@tryghost/errors');
|
||||||
|
const constants = require('@tryghost/constants');
|
||||||
|
const urlUtils = require('../../../shared/url-utils');
|
||||||
|
const StorageBase = require('ghost-storage-base');
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
notFound: 'File not found',
|
||||||
|
notFoundWithRef: 'File not found: {file}',
|
||||||
|
cannotRead: 'Could not read file: {file}'
|
||||||
|
};
|
||||||
|
|
||||||
|
class LocalStorageBase extends StorageBase {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {String} options.storagePath
|
||||||
|
* @param {String} options.staticFileURLPrefix
|
||||||
|
* @param {Object} [options.errorMessages]
|
||||||
|
* @param {String} [options.errorMessages.notFound]
|
||||||
|
* @param {String} [options.errorMessages.notFoundWithRef]
|
||||||
|
* @param {String} [options.errorMessages.cannotRead]
|
||||||
|
*/
|
||||||
|
constructor({storagePath, staticFileURLPrefix, errorMessages}) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.storagePath = storagePath;
|
||||||
|
this.staticFileURLPrefix = staticFileURLPrefix;
|
||||||
|
this.errorMessages = errorMessages || messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the file to storage (the file system)
|
||||||
|
* - returns a promise which ultimately returns the full url to the uploaded file
|
||||||
|
*
|
||||||
|
* @param {StorageBase.Image} file
|
||||||
|
* @param {String} targetDir
|
||||||
|
* @returns {Promise<String>}
|
||||||
|
*/
|
||||||
|
async save(file, targetDir) {
|
||||||
|
let targetFilename;
|
||||||
|
|
||||||
|
// NOTE: the base implementation of `getTargetDir` returns the format this.storagePath/YYYY/MM
|
||||||
|
targetDir = targetDir || this.getTargetDir(this.storagePath);
|
||||||
|
|
||||||
|
const filename = await this.getUniqueFileName(file, targetDir);
|
||||||
|
|
||||||
|
targetFilename = filename;
|
||||||
|
await fs.mkdirs(targetDir);
|
||||||
|
|
||||||
|
await fs.copy(file.path, targetFilename);
|
||||||
|
|
||||||
|
// The src for the image must be in URI format, not a file system path, which in Windows uses \
|
||||||
|
// For local file system storage can use relative path so add a slash
|
||||||
|
const fullUrl = (
|
||||||
|
urlUtils.urlJoin('/',
|
||||||
|
urlUtils.getSubdir(),
|
||||||
|
this.staticFileURLPrefix,
|
||||||
|
path.relative(this.storagePath, targetFilename))
|
||||||
|
).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
|
||||||
|
|
||||||
|
return fullUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
exists(fileName, targetDir) {
|
||||||
|
const filePath = path.join(targetDir || this.storagePath, fileName);
|
||||||
|
|
||||||
|
return fs.stat(filePath)
|
||||||
|
.then(() => {
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For some reason send divides the max age number by 1000
|
||||||
|
* Fallthrough: false ensures that if an image isn't found, it automatically 404s
|
||||||
|
* Wrap server static errors
|
||||||
|
*
|
||||||
|
* @returns {serveStaticContent}
|
||||||
|
*/
|
||||||
|
serve() {
|
||||||
|
const {storagePath, errorMessages} = this;
|
||||||
|
|
||||||
|
return function serveStaticContent(req, res, next) {
|
||||||
|
const startedAtMoment = moment();
|
||||||
|
|
||||||
|
return serveStatic(
|
||||||
|
storagePath,
|
||||||
|
{
|
||||||
|
maxAge: constants.ONE_YEAR_MS,
|
||||||
|
fallthrough: false,
|
||||||
|
onEnd: () => {
|
||||||
|
logging.info('LocalFileStorage.serve', req.path, moment().diff(startedAtMoment, 'ms') + 'ms');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)(req, res, (err) => {
|
||||||
|
if (err) {
|
||||||
|
if (err.statusCode === 404) {
|
||||||
|
return next(new errors.NotFoundError({
|
||||||
|
message: tpl(errorMessages.notFound),
|
||||||
|
code: 'STATIC_FILE_NOT_FOUND',
|
||||||
|
property: err.path
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.statusCode === 400) {
|
||||||
|
return next(new errors.BadRequestError({err: err}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.statusCode === 403) {
|
||||||
|
return next(new errors.NoPermissionError({err: err}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(new errors.GhostError({err: err}));
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not implemented.
|
||||||
|
* @returns {Promise.<*>}
|
||||||
|
*/
|
||||||
|
delete() {
|
||||||
|
return Promise.reject('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads bytes from disk for a target file
|
||||||
|
* - path of target file (without content path!)
|
||||||
|
*
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
read(options) {
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
// remove trailing slashes
|
||||||
|
options.path = (options.path || '').replace(/\/$|\\$/, '');
|
||||||
|
|
||||||
|
const targetPath = path.join(this.storagePath, options.path);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(targetPath, (err, bytes) => {
|
||||||
|
if (err) {
|
||||||
|
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
|
||||||
|
return reject(new errors.NotFoundError({
|
||||||
|
err: err,
|
||||||
|
message: tpl(this.errorMessages.notFoundWithRef, {file: options.path})
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.code === 'ENAMETOOLONG') {
|
||||||
|
return reject(new errors.BadRequestError({err: err}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.code === 'EACCES') {
|
||||||
|
return reject(new errors.NoPermissionError({err: err}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return reject(new errors.GhostError({
|
||||||
|
err: err,
|
||||||
|
message: tpl(this.errorMessages.cannotRead, {file: options.path})
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(bytes);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = LocalStorageBase;
|
Loading…
Add table
Reference in a new issue