mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-22 18:31:57 +03:00
d541a14826
- Currently theme uploads delete the existing theme before copying the new files into place - If something goes wrong with the delete action, you will end up in a bad state - Some or all of the files may be deleted, but now Ghost won't try to put the new theme in place, instead returning an error - This leaves you with an invalid active theme and a broken site - Unlike delete, move is a one-hit operation that succeeds or fails, there moving a theme is safer than deleting - This updated code moves the old theme to a folder with the name [theme-name]-[uuid] before copying the new theme into place - Even if this fails, the files should not be gone - There's a cleanup operation to remove the theme backup at the end, but we don't care too much if this fails
84 lines
2.3 KiB
JavaScript
84 lines
2.3 KiB
JavaScript
const fs = require('fs-extra');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const config = require('../../../shared/config');
|
|
const security = require('../../../server/lib/security');
|
|
const {compress} = require('@tryghost/zip');
|
|
const LocalFileStorage = require('../../../server/adapters/storage/LocalFileStorage');
|
|
|
|
/**
|
|
* @TODO: combine with loader.js?
|
|
*/
|
|
class ThemeStorage extends LocalFileStorage {
|
|
constructor() {
|
|
super();
|
|
|
|
this.storagePath = config.getContentPath('themes');
|
|
}
|
|
|
|
getTargetDir() {
|
|
return this.storagePath;
|
|
}
|
|
|
|
serve(options) {
|
|
const self = this;
|
|
|
|
return function downloadTheme(req, res, next) {
|
|
const themeName = options.name;
|
|
const themePath = path.join(self.storagePath, themeName);
|
|
const zipName = themeName + '.zip';
|
|
|
|
// store this in a unique temporary folder
|
|
const zipBasePath = path.join(os.tmpdir(), security.identifier.uid(10));
|
|
|
|
const zipPath = path.join(zipBasePath, zipName);
|
|
let stream;
|
|
|
|
fs.ensureDir(zipBasePath)
|
|
.then(function () {
|
|
return compress(themePath, zipPath);
|
|
})
|
|
.then(function (result) {
|
|
res.set({
|
|
'Content-disposition': 'attachment; filename={themeName}.zip'.replace('{themeName}', themeName),
|
|
'Content-Type': 'application/zip',
|
|
'Content-Length': result.size
|
|
});
|
|
|
|
stream = fs.createReadStream(zipPath);
|
|
stream.pipe(res);
|
|
})
|
|
.catch(function (err) {
|
|
next(err);
|
|
})
|
|
.finally(function () {
|
|
return fs.remove(zipBasePath);
|
|
});
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Rename a file / folder
|
|
*
|
|
*
|
|
* @param String fileName
|
|
*/
|
|
rename(srcName, destName) {
|
|
let src = path.join(this.getTargetDir(), srcName);
|
|
let dest = path.join(this.getTargetDir(), destName);
|
|
|
|
return fs.move(src, dest);
|
|
}
|
|
|
|
/**
|
|
* Rename a file / folder
|
|
*
|
|
* @param String backupName
|
|
*/
|
|
delete(fileName) {
|
|
return fs.remove(path.join(this.getTargetDir(), fileName));
|
|
}
|
|
}
|
|
|
|
module.exports = ThemeStorage;
|