jp (blog): drafts are now put in a sub dir per profile + added a security which keep the last 10 drafts before removing them (all profiles included).
This commit is contained in:
parent
787a84c29d
commit
2fd6fa663c
1 changed files with 61 additions and 11 deletions
|
|
@ -33,6 +33,7 @@ import subprocess
|
|||
import shlex
|
||||
import glob
|
||||
from sat.tools.common import data_format
|
||||
from sat.tools.common import regex
|
||||
|
||||
__commands__ = ["Blog"]
|
||||
|
||||
|
|
@ -62,29 +63,76 @@ KEY_TO_REMOVE_METADATA = ('id','content', 'content_xhtml', 'comments_node', 'com
|
|||
|
||||
URL_REDIRECT_PREFIX = 'url_redirect_'
|
||||
INOTIFY_INSTALL = '"pip install inotify"'
|
||||
SECURE_UNLINK_MAX = 10 * 2 # we double value has there are 2 files per draft (content and metadata)
|
||||
SECURE_UNLINK_DIR = ".backup"
|
||||
|
||||
|
||||
class BlogCommon(object):
|
||||
def getTmpDir(self, sat_conf):
|
||||
|
||||
def __init__(self, host):
|
||||
self.host = host
|
||||
|
||||
def getTmpDir(self, sat_conf, sub_dir=None):
|
||||
"""Return directory used to store temporary files
|
||||
|
||||
@param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration
|
||||
@param sub_dir(str): sub directory where data need to be put
|
||||
profile can be used here, or special directory name
|
||||
sub_dir will be escaped to be usable in path (use regex.pathUnescape to find
|
||||
initial str)
|
||||
@return (str): path to the dir
|
||||
"""
|
||||
local_dir = config.getConfig(sat_conf, '', 'local_dir', Exception)
|
||||
return os.path.join(local_dir, BLOG_TMP_DIR)
|
||||
path = [local_dir, BLOG_TMP_DIR]
|
||||
if sub_dir is not None:
|
||||
path.append(regex.pathEscape(sub_dir))
|
||||
return os.path.join(*path)
|
||||
|
||||
def getCurrentFile(self, sat_conf):
|
||||
def getCurrentFile(self, sat_conf, profile):
|
||||
"""Get most recently edited file
|
||||
|
||||
@param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration
|
||||
@param profile(unicode): profile linked to the blog draft
|
||||
@return(str): full path of current file
|
||||
"""
|
||||
# we guess the blog item currently edited by choosing
|
||||
# the most recent file corresponding to temp file pattern
|
||||
# in tmp_dir, excluding metadata files
|
||||
tmp_dir = self.getTmpDir(sat_conf)
|
||||
tmp_dir = self.getTmpDir(sat_conf, profile.encode('utf-8'))
|
||||
available = [path for path in glob.glob(os.path.join(tmp_dir, 'blog_*')) if not path.endswith(METADATA_SUFF)]
|
||||
if not available:
|
||||
self.disp(u"Counldn't find any content draft in {path}".format(path=tmp_dir), error=True)
|
||||
self.host.quit(1)
|
||||
return max(available, key=lambda path: os.stat(path).st_mtime)
|
||||
|
||||
def secureUnlink(self, sat_conf, path):
|
||||
"""Unlink given path after keeping it for a while
|
||||
|
||||
This method is used to prevent accidental deletion of a blog draft
|
||||
If there are more file in SECURE_UNLINK_DIR than SECURE_UNLINK_MAX,
|
||||
older file are deleted
|
||||
@param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration
|
||||
@param path(str): file to unlink
|
||||
"""
|
||||
if not os.path.isfile(path):
|
||||
raise OSError(u"path must link to a regular file")
|
||||
backup_dir = self.getTmpDir(sat_conf, SECURE_UNLINK_DIR)
|
||||
if not os.path.exists(backup_dir):
|
||||
os.makedirs(backup_dir)
|
||||
filename = os.path.basename(path)
|
||||
backup_path = os.path.join(backup_dir, filename)
|
||||
# we move file to backup dir
|
||||
self.host.disp(u"Backuping file {src} to {dst}".format(
|
||||
src=path.decode('utf-8'), dst=backup_path.decode('utf-8')), 1)
|
||||
os.rename(path, backup_path)
|
||||
# and if we exceeded the limit, we remove older file
|
||||
backup_files = [os.path.join(backup_dir, f) for f in os.listdir(backup_dir)]
|
||||
if len(backup_files) > SECURE_UNLINK_MAX:
|
||||
backup_files.sort(key=lambda path: os.stat(path).st_mtime)
|
||||
for path in backup_files[:len(backup_files) - SECURE_UNLINK_MAX]:
|
||||
self.host.disp(u"Purging backup file {}".format(path.decode('utf-8')), 2)
|
||||
os.unlink(path)
|
||||
|
||||
def guessSyntaxFromPath(self, sat_conf, path):
|
||||
"""Return syntax guessed according to filename extension
|
||||
|
||||
|
|
@ -121,7 +169,8 @@ class BlogCommon(object):
|
|||
class Edit(base.CommandBase, BlogCommon):
|
||||
|
||||
def __init__(self, host):
|
||||
super(Edit, self).__init__(host, 'edit', use_verbose=True, help=_(u'edit an existing or new blog post'))
|
||||
base.CommandBase.__init__(self, host, 'edit', use_verbose=True, help=_(u'edit an existing or new blog post'))
|
||||
BlogCommon.__init__(self, self.host)
|
||||
|
||||
def add_parser_options(self):
|
||||
self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=u'new', help=_(u"URL of the item to edit, or keyword"))
|
||||
|
|
@ -137,7 +186,7 @@ class Edit(base.CommandBase, BlogCommon):
|
|||
@param tmp_suff (str): suffix to use for the filename
|
||||
@return (tuple(file, str)): opened (w+b) file object and file path
|
||||
"""
|
||||
tmp_dir = self.getTmpDir(sat_conf)
|
||||
tmp_dir = self.getTmpDir(sat_conf, self.profile.encode('utf-8'))
|
||||
if not os.path.exists(tmp_dir):
|
||||
try:
|
||||
os.makedirs(tmp_dir)
|
||||
|
|
@ -283,8 +332,8 @@ class Edit(base.CommandBase, BlogCommon):
|
|||
else:
|
||||
self.disp(u"Blog item published")
|
||||
|
||||
os.unlink(content_file_path)
|
||||
os.unlink(meta_file_path)
|
||||
self.secureUnlink(sat_conf, content_file_path)
|
||||
self.secureUnlink(sat_conf, meta_file_path)
|
||||
|
||||
def start(self):
|
||||
item_lower = self.args.item.lower()
|
||||
|
|
@ -319,7 +368,7 @@ class Edit(base.CommandBase, BlogCommon):
|
|||
mb_data = None
|
||||
if item_lower == 'current':
|
||||
# use wants to continue current draft
|
||||
content_file_path = self.getCurrentFile(sat_conf)
|
||||
content_file_path = self.getCurrentFile(sat_conf, self.profile)
|
||||
self.disp(u'Continuing edition of current draft', 2)
|
||||
else:
|
||||
# for now we taxe the item as a file path
|
||||
|
|
@ -334,7 +383,8 @@ class Edit(base.CommandBase, BlogCommon):
|
|||
class Preview(base.CommandBase, BlogCommon):
|
||||
|
||||
def __init__(self, host):
|
||||
super(Preview, self).__init__(host, 'preview', use_verbose=True, help=_(u'preview a blog content'))
|
||||
base.CommandBase.__init__(self, host, 'preview', use_verbose=True, help=_(u'preview a blog content'))
|
||||
BlogCommon.__init__(self, self.host)
|
||||
|
||||
def add_parser_options(self):
|
||||
self.parser.add_argument("--inotify", type=str, choices=('auto', 'true', 'false'), default=u'auto', help=_(u"use inotify to handle preview"))
|
||||
|
|
@ -421,7 +471,7 @@ class Preview(base.CommandBase, BlogCommon):
|
|||
|
||||
# which file do we need to edit?
|
||||
if self.args.file == 'current':
|
||||
self.content_file_path = self.getCurrentFile(sat_conf)
|
||||
self.content_file_path = self.getCurrentFile(sat_conf, self.profile)
|
||||
else:
|
||||
self.content_file_path = os.path.abspath(self.args.file)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue