jp (blog/edit): added metadata handling through a .json file (named like content temporary file, but with extension replaced by "_metadata.json"). Modification to this file before the end of edition will be taken into account.
This commit is contained in:
parent
b28dcdbfb3
commit
272d6f84d6
1 changed files with 84 additions and 31 deletions
|
|
@ -38,6 +38,8 @@ SYNTAX_EXT = { '': 'txt', # used when the syntax is not found
|
|||
}
|
||||
CONF_SYNTAX_EXT = 'syntax_ext_dict'
|
||||
BLOG_TMP_DIR="blog"
|
||||
# key to remove from metadata tmp file if they exist
|
||||
KEY_TO_REMOVE_METADATA = ('id','content', 'content_rich', 'content_xhtml', 'comments_node', 'comments_service')
|
||||
|
||||
URL_REDIRECT_PREFIX = 'url_redirect_'
|
||||
|
||||
|
|
@ -54,6 +56,12 @@ class Edit(base.CommandBase):
|
|||
self.parser.add_argument("--no-comment", action='store_true', help=_(u"disable comments"))
|
||||
|
||||
def getTmpFile(self, sat_conf, tmp_suff):
|
||||
"""Create a temporary file to received blog item body
|
||||
|
||||
@param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration
|
||||
@param tmp_suff (str): suffix to use for the filename
|
||||
@return (tuple(file, str)): opened (w+b) file object and file path
|
||||
"""
|
||||
local_dir = config.getConfig(sat_conf, '', 'local_dir', Exception)
|
||||
tmp_dir = os.path.join(local_dir, BLOG_TMP_DIR)
|
||||
if not os.path.exists(tmp_dir):
|
||||
|
|
@ -63,63 +71,109 @@ class Edit(base.CommandBase):
|
|||
self.disp(u"Can't create {path} directory: {reason}".format(
|
||||
path=tmp_dir, reason=e), error=True)
|
||||
self.host.quit(1)
|
||||
|
||||
try:
|
||||
return tempfile.mkstemp(suffix=tmp_suff,
|
||||
fd, path = tempfile.mkstemp(suffix=tmp_suff,
|
||||
prefix=time.strftime('blog_%Y-%m-%d_%H:%M:%S_'),
|
||||
dir=tmp_dir, text=True)
|
||||
return os.fdopen(fd, 'w+b'), path
|
||||
except OSError as e:
|
||||
self.disp(u"Can't create temporary file: {reason}".format(reason=e), error=True)
|
||||
self.host.quit(1)
|
||||
|
||||
def edit(self, sat_conf, tmp_file, tmp_file_obj, item_id=None):
|
||||
def buildMetadataFile(self, tmp_file_path, mb_data=None):
|
||||
"""Build a metadata file using json
|
||||
|
||||
The file is named after tmp_file_path, with extension replaced by _metadata.json
|
||||
@param tmp_file_path(str): path to the temporary file which will contain the body
|
||||
@param mb_data(dict, None): microblog metadata (for existing items)
|
||||
@return (tuple[dict, str]): merged metadata put originaly in metadata file
|
||||
and path to temporary metadata file
|
||||
"""
|
||||
# we first construct metadata from edited item ones and CLI argumments
|
||||
mb_data = {} if mb_data is None else mb_data.copy()
|
||||
for key in KEY_TO_REMOVE_METADATA:
|
||||
try:
|
||||
del mb_data[key]
|
||||
except KeyError:
|
||||
pass
|
||||
mb_data['allow_comments'] = C.boolConst(not self.args.no_comment)
|
||||
if self.args.tag:
|
||||
common.iter2dict('tag', self.args.tag, mb_data)
|
||||
if self.args.title is not None:
|
||||
mb_data['title'] = self.args.title
|
||||
|
||||
# the we create the file and write metadata there, as JSON dict
|
||||
meta_file_path = os.path.splitext(tmp_file_path)[0] + '_metadata.json'
|
||||
# XXX: if we port jp one day on Windows, O_BINARY may need to be added here
|
||||
if os.path.exists(meta_file_path):
|
||||
self.disp(u"metadata file {} already exists, this should not happen! Cancelling...", error=True)
|
||||
with os.fdopen(os.open(meta_file_path, os.O_RDWR | os.O_CREAT ,0o600), 'w+b') as f:
|
||||
# we need to use an intermediate unicode buffer to write to the file unicode without escaping characters
|
||||
unicode_dump = json.dumps(mb_data, ensure_ascii=False, indent=4, separators=(',', ': '), sort_keys=True)
|
||||
f.write(unicode_dump.encode('utf-8'))
|
||||
|
||||
return mb_data, meta_file_path
|
||||
|
||||
def edit(self, sat_conf, tmp_file_path, tmp_file_obj, mb_data=None):
|
||||
"""Edit the file contening the content using editor, and publish it"""
|
||||
# we first calculate hash to check for modifications
|
||||
item_ori_mb_data = mb_data
|
||||
# we first create metadata file
|
||||
meta_ori, meta_file_path = self.buildMetadataFile(tmp_file_path, item_ori_mb_data)
|
||||
|
||||
# then we calculate hashes to check for modifications
|
||||
import hashlib
|
||||
tmp_file_obj.seek(0)
|
||||
ori_hash = hashlib.sha1(tmp_file_obj.read()).digest()
|
||||
tmp_ori_hash = hashlib.sha1(tmp_file_obj.read()).digest()
|
||||
tmp_file_obj.close()
|
||||
|
||||
# the we launch editor
|
||||
# then we launch editor
|
||||
editor = config.getConfig(sat_conf, 'jp', 'editor') or os.getenv('EDITOR', 'vi')
|
||||
editor_exit = subprocess.call([editor, tmp_file])
|
||||
editor_exit = subprocess.call([editor, tmp_file_path])
|
||||
|
||||
# we send the file if edition was a success
|
||||
if editor_exit != 0:
|
||||
self.disp(u"Editor exited with an error code, so temporary file has not be deleted, and blog item is not published.\nTou can find temporary file at {path}".format(
|
||||
path=tmp_file), error=True)
|
||||
path=tmp_file_path), error=True)
|
||||
else:
|
||||
with open(tmp_file, 'rb') as f:
|
||||
content = f.read()
|
||||
try:
|
||||
with open(tmp_file_path, 'rb') as f:
|
||||
content = f.read()
|
||||
with open(meta_file_path, 'rb') as f:
|
||||
mb_data = json.load(f)
|
||||
except OSError:
|
||||
self.disp(u"Can write files at {file_path} and/or {meta_path}, have they been deleted? Cancelling edition".format(
|
||||
file_path=tmp_file_path, meta_path=meta_file_path), error=True)
|
||||
self.host.quit(1)
|
||||
except ValueError:
|
||||
self.disp(u"Can't parse metadata, please check it is correct JSON format. Cancelling edition.\n" +
|
||||
"You can find tmp file at {file_path} and temporary meta file at {meta_path}.".format(
|
||||
file_path=tmp_file_path, meta_path=meta_file_path), error=True)
|
||||
self.host.quit(1)
|
||||
|
||||
if len(content) == 0:
|
||||
self.disp(u"Content is empty, cancelling the blog edition")
|
||||
|
||||
# time to re-check the hash
|
||||
elif ori_hash == hashlib.sha1(content).digest():
|
||||
elif (tmp_ori_hash == hashlib.sha1(content).digest() and
|
||||
meta_ori == mb_data):
|
||||
self.disp(u"The content has not been modified, cancelling the blog edition")
|
||||
|
||||
else:
|
||||
# we can now send the blog
|
||||
mb_data = {
|
||||
'content_rich': content.decode('utf-8'),
|
||||
'allow_comments': C.boolConst(not self.args.no_comment),
|
||||
}
|
||||
if item_id:
|
||||
mb_data['id'] = item_id
|
||||
if self.args.tag:
|
||||
common.iter2dict('tag', self.args.tag, mb_data)
|
||||
mb_data['content_rich'] = content.decode('utf-8')
|
||||
|
||||
if item_ori_mb_data is not None:
|
||||
mb_data['id'] = item_ori_mb_data['id']
|
||||
|
||||
if self.args.title is not None:
|
||||
mb_data['title'] = self.args.title
|
||||
try:
|
||||
self.host.bridge.mbSend('', '', mb_data, self.profile)
|
||||
except Exception as e:
|
||||
self.disp(u"Error while sending your blog, the temporary file has been kept at {path}: {reason}".format(
|
||||
path=tmp_file, reason=e), error=True)
|
||||
self.disp(u"Error while sending your blog, the temporary files have been kept at {file_path} and {meta_path}: {reason}".format(
|
||||
file_path=tmp_file_path, meta_path=meta_file_path, reason=e), error=True)
|
||||
self.host.quit(1)
|
||||
|
||||
os.unlink(tmp_file)
|
||||
os.unlink(tmp_file_path)
|
||||
os.unlink(meta_file_path)
|
||||
|
||||
def start(self):
|
||||
# we get current syntax to determine file extension
|
||||
|
|
@ -131,12 +185,12 @@ class Edit(base.CommandBase):
|
|||
|
||||
# we now create a temporary file
|
||||
tmp_suff = '.' + SYNTAX_EXT.get(current_syntax, SYNTAX_EXT[''])
|
||||
fd, tmp_file = self.getTmpFile(sat_conf, tmp_suff)
|
||||
tmp_file_obj, tmp_file_path = self.getTmpFile(sat_conf, tmp_suff)
|
||||
|
||||
item_lower = self.args.item.lower()
|
||||
if item_lower == 'new':
|
||||
self.disp(u'Editing a new blog item', 2)
|
||||
self.edit(sat_conf, tmp_file, os.fdopen(fd))
|
||||
self.edit(sat_conf, tmp_file_path, tmp_file_obj)
|
||||
elif item_lower == 'last':
|
||||
self.disp(u'Editing last published item', 2)
|
||||
try:
|
||||
|
|
@ -148,10 +202,9 @@ class Edit(base.CommandBase):
|
|||
content = mb_data['content_xhtml']
|
||||
if current_syntax != 'XHTML':
|
||||
content = self.host.bridge.syntaxConvert(content, 'XHTML', current_syntax, False, self.profile)
|
||||
f = os.fdopen(fd, 'w+b')
|
||||
f.write(content.encode('utf-8'))
|
||||
f.seek(0)
|
||||
self.edit(sat_conf, tmp_file, f, mb_data['id'])
|
||||
tmp_file_obj.write(content.encode('utf-8'))
|
||||
tmp_file_obj.seek(0)
|
||||
self.edit(sat_conf, tmp_file_path, tmp_file_obj, mb_data=mb_data)
|
||||
|
||||
|
||||
class Import(base.CommandAnswering):
|
||||
|
|
|
|||
Loading…
Reference in a new issue