Mercurial > libervia-backend
comparison frontends/src/jp/cmd_blog.py @ 2269:606ff34d30f2
jp (blog, common): moved and improved edit code from blog:
- a new "common" module is there for code commonly used in commands
- moved code for editing item with $EDITOR there
- moved code to identify item to edit there
- aforementioned fontions have been made generic
- a class BaseEdit is now available to implement edition
- HTTPS links are handled (only HTTP links were working before)
- item can be use if all previous methods fail (url, keyword, file path).
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 27 Jun 2017 16:23:28 +0200 |
parents | ed28798fd76c |
children | 07caa12be945 |
comparison
equal
deleted
inserted
replaced
2268:a29d1351bc83 | 2269:606ff34d30f2 |
---|---|
19 | 19 |
20 | 20 |
21 import base | 21 import base |
22 from sat.core.i18n import _ | 22 from sat.core.i18n import _ |
23 from sat_frontends.jp.constants import Const as C | 23 from sat_frontends.jp.constants import Const as C |
24 from sat_frontends.jp import common | |
24 from sat.tools.common.ansi import ANSI as A | 25 from sat.tools.common.ansi import ANSI as A |
25 from sat.tools.common import data_objects | 26 from sat.tools.common import data_objects |
26 from sat.tools import config | 27 from sat.tools import config |
27 from ConfigParser import NoSectionError, NoOptionError | 28 from ConfigParser import NoSectionError, NoOptionError |
28 import json | 29 import json |
31 import os | 32 import os |
32 import time | 33 import time |
33 import tempfile | 34 import tempfile |
34 import subprocess | 35 import subprocess |
35 import shlex | 36 import shlex |
36 import glob | |
37 from sat.tools.common import data_format | 37 from sat.tools.common import data_format |
38 from sat.tools.common import regex | |
39 | 38 |
40 __commands__ = ["Blog"] | 39 __commands__ = ["Blog"] |
41 | 40 |
42 # extensions to use with known syntaxes | 41 # extensions to use with known syntaxes |
43 SYNTAX_EXT = { | 42 SYNTAX_EXT = { |
44 '': 'txt', # used when the syntax is not found | 43 '': 'txt', # used when the syntax is not found |
45 "XHTML": "xhtml", | 44 "XHTML": "xhtml", |
46 "markdown": "md" | 45 "markdown": "md" |
47 } | 46 } |
48 | 47 |
49 # defaut arguments used for some known editors | |
50 VIM_SPLIT_ARGS = "-c 'vsplit|wincmd w|next|wincmd w'" | |
51 EMACS_SPLIT_ARGS = '--eval "(split-window-horizontally)"' | |
52 EDITOR_ARGS_MAGIC = { | |
53 'vim': VIM_SPLIT_ARGS + ' {content_file} {metadata_file}', | |
54 'gvim': VIM_SPLIT_ARGS + ' --nofork {content_file} {metadata_file}', | |
55 'emacs': EMACS_SPLIT_ARGS + ' {content_file} {metadata_file}', | |
56 'xemacs': EMACS_SPLIT_ARGS + ' {content_file} {metadata_file}', | |
57 'nano': ' -F {content_file} {metadata_file}', | |
58 } | |
59 | 48 |
60 CONF_SYNTAX_EXT = 'syntax_ext_dict' | 49 CONF_SYNTAX_EXT = 'syntax_ext_dict' |
61 BLOG_TMP_DIR="blog" | 50 BLOG_TMP_DIR=u"blog" |
62 METADATA_SUFF = '_metadata.json' | |
63 # key to remove from metadata tmp file if they exist | 51 # key to remove from metadata tmp file if they exist |
64 KEY_TO_REMOVE_METADATA = ('id','content', 'content_xhtml', 'comments_node', 'comments_service', 'updated') | 52 KEY_TO_REMOVE_METADATA = ('id','content', 'content_xhtml', 'comments_node', 'comments_service', 'updated') |
65 | 53 |
66 URL_REDIRECT_PREFIX = 'url_redirect_' | 54 URL_REDIRECT_PREFIX = 'url_redirect_' |
67 INOTIFY_INSTALL = '"pip install inotify"' | 55 INOTIFY_INSTALL = '"pip install inotify"' |
68 SECURE_UNLINK_MAX = 10 * 2 # we double value as there are 2 files per draft (content and metadata) | |
69 SECURE_UNLINK_DIR = ".backup" | |
70 MB_KEYS = (u"id", | 56 MB_KEYS = (u"id", |
71 u"atom_id", | 57 u"atom_id", |
72 u"updated", | 58 u"updated", |
73 u"published", | 59 u"published", |
74 u"language", | 60 u"language", |
91 self.host = host | 77 self.host = host |
92 | 78 |
93 def addServiceNodeArgs(self): | 79 def addServiceNodeArgs(self): |
94 self.parser.add_argument("-n", "--node", type=base.unicode_decoder, default=u'', help=_(u"PubSub node to request (default: microblog namespace)")) | 80 self.parser.add_argument("-n", "--node", type=base.unicode_decoder, default=u'', help=_(u"PubSub node to request (default: microblog namespace)")) |
95 self.parser.add_argument("-s", "--service", type=base.unicode_decoder, default=u'', help=_(u"JID of the PubSub service (default: request profile own blog)")) | 81 self.parser.add_argument("-s", "--service", type=base.unicode_decoder, default=u'', help=_(u"JID of the PubSub service (default: request profile own blog)")) |
96 | |
97 def getTmpDir(self, sat_conf, sub_dir=None): | |
98 """Return directory used to store temporary files | |
99 | |
100 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration | |
101 @param sub_dir(str): sub directory where data need to be put | |
102 profile can be used here, or special directory name | |
103 sub_dir will be escaped to be usable in path (use regex.pathUnescape to find | |
104 initial str) | |
105 @return (str): path to the dir | |
106 """ | |
107 local_dir = config.getConfig(sat_conf, '', 'local_dir', Exception) | |
108 path = [local_dir, BLOG_TMP_DIR] | |
109 if sub_dir is not None: | |
110 path.append(regex.pathEscape(sub_dir)) | |
111 return os.path.join(*path) | |
112 | |
113 def getCurrentFile(self, sat_conf, profile): | |
114 """Get most recently edited file | |
115 | |
116 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration | |
117 @param profile(unicode): profile linked to the blog draft | |
118 @return(str): full path of current file | |
119 """ | |
120 # we guess the blog item currently edited by choosing | |
121 # the most recent file corresponding to temp file pattern | |
122 # in tmp_dir, excluding metadata files | |
123 tmp_dir = self.getTmpDir(sat_conf, profile.encode('utf-8')) | |
124 available = [path for path in glob.glob(os.path.join(tmp_dir, 'blog_*')) if not path.endswith(METADATA_SUFF)] | |
125 if not available: | |
126 self.disp(u"Counldn't find any content draft in {path}".format(path=tmp_dir), error=True) | |
127 self.host.quit(1) | |
128 return max(available, key=lambda path: os.stat(path).st_mtime) | |
129 | |
130 def secureUnlink(self, sat_conf, path): | |
131 """Unlink given path after keeping it for a while | |
132 | |
133 This method is used to prevent accidental deletion of a blog draft | |
134 If there are more file in SECURE_UNLINK_DIR than SECURE_UNLINK_MAX, | |
135 older file are deleted | |
136 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration | |
137 @param path(str): file to unlink | |
138 """ | |
139 if not os.path.isfile(path): | |
140 raise OSError(u"path must link to a regular file") | |
141 if not path.startswith(self.getTmpDir(sat_conf)): | |
142 self.disp(u"File {} is not in blog temporary hierarchy, we do not remove it".format(path.decode('utf-8')), 2) | |
143 return | |
144 backup_dir = self.getTmpDir(sat_conf, SECURE_UNLINK_DIR) | |
145 if not os.path.exists(backup_dir): | |
146 os.makedirs(backup_dir) | |
147 filename = os.path.basename(path) | |
148 backup_path = os.path.join(backup_dir, filename) | |
149 # we move file to backup dir | |
150 self.host.disp(u"Backuping file {src} to {dst}".format( | |
151 src=path.decode('utf-8'), dst=backup_path.decode('utf-8')), 1) | |
152 os.rename(path, backup_path) | |
153 # and if we exceeded the limit, we remove older file | |
154 backup_files = [os.path.join(backup_dir, f) for f in os.listdir(backup_dir)] | |
155 if len(backup_files) > SECURE_UNLINK_MAX: | |
156 backup_files.sort(key=lambda path: os.stat(path).st_mtime) | |
157 for path in backup_files[:len(backup_files) - SECURE_UNLINK_MAX]: | |
158 self.host.disp(u"Purging backup file {}".format(path.decode('utf-8')), 2) | |
159 os.unlink(path) | |
160 | 82 |
161 def guessSyntaxFromPath(self, sat_conf, path): | 83 def guessSyntaxFromPath(self, sat_conf, path): |
162 """Return syntax guessed according to filename extension | 84 """Return syntax guessed according to filename extension |
163 | 85 |
164 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration | 86 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration |
363 self.profile, | 285 self.profile, |
364 callback=self.mbGetCb, | 286 callback=self.mbGetCb, |
365 errback=self.mbGetEb) | 287 errback=self.mbGetEb) |
366 | 288 |
367 | 289 |
368 class Edit(base.CommandBase, BlogCommon): | 290 class Edit(base.CommandBase, BlogCommon, common.BaseEdit): |
369 | 291 |
370 def __init__(self, host): | 292 def __init__(self, host): |
371 base.CommandBase.__init__(self, host, 'edit', use_verbose=True, help=_(u'edit an existing or new blog post')) | 293 base.CommandBase.__init__(self, host, 'edit', use_verbose=True, help=_(u'edit an existing or new blog post')) |
372 BlogCommon.__init__(self, self.host) | 294 BlogCommon.__init__(self, self.host) |
295 common.BaseEdit.__init__(self, self.host, BLOG_TMP_DIR, use_metadata=True) | |
373 | 296 |
374 def add_parser_options(self): | 297 def add_parser_options(self): |
375 self.addServiceNodeArgs() | 298 self.addServiceNodeArgs() |
376 self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=u'new', help=_(u"URL of the item to edit, or keyword")) | 299 self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=u'new', help=_(u"URL of the item to edit, or keyword")) |
377 self.parser.add_argument("-P", "--preview", action="store_true", help=_(u"launch a blog preview in parallel")) | 300 self.parser.add_argument("-P", "--preview", action="store_true", help=_(u"launch a blog preview in parallel")) |
378 self.parser.add_argument("-T", '--title', type=base.unicode_decoder, help=_(u"title of the item")) | 301 self.parser.add_argument("-T", '--title', type=base.unicode_decoder, help=_(u"title of the item")) |
379 self.parser.add_argument("-t", '--tag', type=base.unicode_decoder, action='append', help=_(u"tag (category) of your item")) | 302 self.parser.add_argument("-t", '--tag', type=base.unicode_decoder, action='append', help=_(u"tag (category) of your item")) |
380 self.parser.add_argument("--no-comment", action='store_true', help=_(u"disable comments")) | 303 self.parser.add_argument("--no-comment", action='store_true', help=_(u"disable comments")) |
381 | |
382 def getTmpFile(self, sat_conf, tmp_suff): | |
383 """Create a temporary file to received blog item body | |
384 | |
385 @param sat_conf(ConfigParser.ConfigParser): instance opened on sat configuration | |
386 @param tmp_suff (str): suffix to use for the filename | |
387 @return (tuple(file, str)): opened (w+b) file object and file path | |
388 """ | |
389 tmp_dir = self.getTmpDir(sat_conf, self.profile.encode('utf-8')) | |
390 if not os.path.exists(tmp_dir): | |
391 try: | |
392 os.makedirs(tmp_dir) | |
393 except OSError as e: | |
394 self.disp(u"Can't create {path} directory: {reason}".format( | |
395 path=tmp_dir, reason=e), error=True) | |
396 self.host.quit(1) | |
397 try: | |
398 fd, path = tempfile.mkstemp(suffix=tmp_suff, | |
399 prefix=time.strftime('blog_%Y-%m-%d_%H:%M:%S_'), | |
400 dir=tmp_dir, text=True) | |
401 return os.fdopen(fd, 'w+b'), path | |
402 except OSError as e: | |
403 self.disp(u"Can't create temporary file: {reason}".format(reason=e), error=True) | |
404 self.host.quit(1) | |
405 | 304 |
406 def buildMetadataFile(self, content_file_path, mb_data=None): | 305 def buildMetadataFile(self, content_file_path, mb_data=None): |
407 """Build a metadata file using json | 306 """Build a metadata file using json |
408 | 307 |
409 The file is named after content_file_path, with extension replaced by _metadata.json | 308 The file is named after content_file_path, with extension replaced by _metadata.json |
412 @return (tuple[dict, str]): merged metadata put originaly in metadata file | 311 @return (tuple[dict, str]): merged metadata put originaly in metadata file |
413 and path to temporary metadata file | 312 and path to temporary metadata file |
414 """ | 313 """ |
415 # we first construct metadata from edited item ones and CLI argumments | 314 # we first construct metadata from edited item ones and CLI argumments |
416 # or re-use the existing one if it exists | 315 # or re-use the existing one if it exists |
417 meta_file_path = os.path.splitext(content_file_path)[0] + METADATA_SUFF | 316 meta_file_path = os.path.splitext(content_file_path)[0] + common.METADATA_SUFF |
418 if os.path.exists(meta_file_path): | 317 if os.path.exists(meta_file_path): |
419 self.disp(u"Metadata file already exists, we re-use it") | 318 self.disp(u"Metadata file already exists, we re-use it") |
420 try: | 319 try: |
421 with open(meta_file_path, 'rb') as f: | 320 with open(meta_file_path, 'rb') as f: |
422 mb_data = json.load(f) | 321 mb_data = json.load(f) |
447 unicode_dump = json.dumps(mb_data, ensure_ascii=False, indent=4, separators=(',', ': '), sort_keys=True) | 346 unicode_dump = json.dumps(mb_data, ensure_ascii=False, indent=4, separators=(',', ': '), sort_keys=True) |
448 f.write(unicode_dump.encode('utf-8')) | 347 f.write(unicode_dump.encode('utf-8')) |
449 | 348 |
450 return mb_data, meta_file_path | 349 return mb_data, meta_file_path |
451 | 350 |
452 def edit(self, sat_conf, content_file_path, content_file_obj, | 351 def edit(self, content_file_path, content_file_obj, |
453 pubsub_service, pubsub_node, mb_data=None): | 352 mb_data=None): |
454 """Edit the file contening the content using editor, and publish it""" | 353 """Edit the file contening the content using editor, and publish it""" |
455 item_ori_mb_data = mb_data | 354 self.item_ori_mb_data = mb_data |
456 # we first create metadata file | 355 # we first create metadata file |
457 meta_ori, meta_file_path = self.buildMetadataFile(content_file_path, item_ori_mb_data) | 356 meta_ori, meta_file_path = self.buildMetadataFile(content_file_path, self.item_ori_mb_data) |
458 | |
459 # then we calculate hashes to check for modifications | |
460 import hashlib | |
461 content_file_obj.seek(0) | |
462 tmp_ori_hash = hashlib.sha1(content_file_obj.read()).digest() | |
463 content_file_obj.close() | |
464 | 357 |
465 # do we need a preview ? | 358 # do we need a preview ? |
466 if self.args.preview: | 359 if self.args.preview: |
467 self.disp(u"Preview requested, launching it", 1) | 360 self.disp(u"Preview requested, launching it", 1) |
468 # we redirect outputs to /dev/null to avoid console pollution in editor | 361 # we redirect outputs to /dev/null to avoid console pollution in editor |
469 # if user wants to see messages, (s)he can call "blog preview" directly | 362 # if user wants to see messages, (s)he can call "blog preview" directly |
470 DEVNULL = open(os.devnull, 'wb') | 363 DEVNULL = open(os.devnull, 'wb') |
471 subprocess.Popen([sys.argv[0], "blog", "preview", "--inotify", "true", "-p", self.profile, content_file_path], stdout=DEVNULL, stderr=subprocess.STDOUT) | 364 subprocess.Popen([sys.argv[0], "blog", "preview", "--inotify", "true", "-p", self.profile, content_file_path], stdout=DEVNULL, stderr=subprocess.STDOUT) |
472 | 365 |
473 # then we launch editor | 366 # we launch editor |
474 editor = config.getConfig(sat_conf, 'jp', 'editor') or os.getenv('EDITOR', 'vi') | 367 self.runEditor("blog_editor_args", content_file_path, content_file_obj, meta_file_path=meta_file_path, meta_ori=meta_ori) |
368 | |
369 def publish(self, content, mb_data): | |
370 mb_data['content_rich'] = content | |
371 | |
372 if self.item_ori_mb_data is not None: | |
373 mb_data['id'] = self.item_ori_mb_data['id'] | |
374 | |
375 self.host.bridge.mbSend(self.pubsub_service, self.pubsub_node, mb_data, self.profile) | |
376 self.disp(u"Blog item published") | |
377 | |
378 def getTmpSuff(self): | |
379 # we get current syntax to determine file extension | |
380 self.current_syntax = self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile) | |
381 return SYNTAX_EXT.get(self.current_syntax, SYNTAX_EXT['']) | |
382 | |
383 def getItemData(self, service, node, item): | |
384 items = [item] if item is not None else [] | |
385 mb_data = self.host.bridge.mbGet(service, node, 1, items, {}, self.profile)[0][0] | |
475 try: | 386 try: |
476 # is there custom arguments in sat.conf ? | 387 content = mb_data['content_xhtml'] |
477 editor_args = config.getConfig(sat_conf, 'jp', 'blog_editor_args', Exception) | 388 except KeyError: |
478 except (NoOptionError, NoSectionError): | 389 content = mb_data['content'] |
479 # no, we check if we know the editor and have special arguments | 390 if content: |
480 editor_args = EDITOR_ARGS_MAGIC.get(os.path.basename(editor), '') | 391 content = self.host.bridge.syntaxConvert(content, 'text', 'XHTML', False, self.profile) |
481 args = self.parse_args(editor_args, content_file=content_file_path, metadata_file=meta_file_path) | 392 if content and self.current_syntax != 'XHTML': |
482 if not args: | 393 content = self.host.bridge.syntaxConvert(content, 'XHTML', self.current_syntax, False, self.profile) |
483 args = [content_file_path] | 394 return content, mb_data |
484 editor_exit = subprocess.call([editor] + args) | |
485 | |
486 # we send the file if edition was a success | |
487 if editor_exit != 0: | |
488 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( | |
489 path=content_file_path), error=True) | |
490 else: | |
491 try: | |
492 with open(content_file_path, 'rb') as f: | |
493 content = f.read() | |
494 with open(meta_file_path, 'rb') as f: | |
495 mb_data = json.load(f) | |
496 except (OSError, IOError): | |
497 self.disp(u"Can read files at {content_path} and/or {meta_path}, have they been deleted?\nCancelling edition".format( | |
498 content_path=content_file_path, meta_path=meta_file_path), error=True) | |
499 self.host.quit(1) | |
500 except ValueError: | |
501 self.disp(u"Can't parse metadata, please check it is correct JSON format. Cancelling edition.\n" + | |
502 "You can find tmp file at {content_path} and temporary meta file at {meta_path}.".format( | |
503 content_path=content_file_path, meta_path=meta_file_path), error=True) | |
504 self.host.quit(1) | |
505 | |
506 if not C.bool(mb_data.get('publish', "true")): | |
507 self.disp(u'Publication blocked by "publish" key in metadata, cancelling edition.\n\n' + | |
508 "temporary file path:\t{content_path}\nmetadata file path:\t{meta_path}".format( | |
509 content_path=content_file_path, meta_path=meta_file_path), error=True) | |
510 self.host.quit(0) | |
511 | |
512 if len(content) == 0: | |
513 self.disp(u"Content is empty, cancelling the blog edition") | |
514 if not content_file_path.startswith(self.getTmpDir(sat_conf)): | |
515 self.disp(u"File are not in blog temporary hierarchy, we do not remove it", 2) | |
516 self.host.quit() | |
517 self.disp(u"Deletion of {}".format(content_file_path.decode('utf-8')), 2) | |
518 os.unlink(content_file_path) | |
519 self.disp(u"Deletion of {}".format(meta_file_path.decode('utf-8')), 2) | |
520 os.unlink(meta_file_path) | |
521 self.host.quit() | |
522 | |
523 # time to re-check the hash | |
524 elif (tmp_ori_hash == hashlib.sha1(content).digest() and | |
525 meta_ori == mb_data): | |
526 self.disp(u"The content has not been modified, cancelling the blog edition") | |
527 | |
528 else: | |
529 # we can now send the blog | |
530 mb_data['content_rich'] = content.decode('utf-8-sig') # we use utf-8-sig to avoid BOM | |
531 | |
532 if item_ori_mb_data is not None: | |
533 mb_data['id'] = item_ori_mb_data['id'] | |
534 | |
535 try: | |
536 self.host.bridge.mbSend(pubsub_service, pubsub_node, mb_data, self.profile) | |
537 except Exception as e: | |
538 self.disp(u"Error while sending your blog, the temporary files have been kept at {content_path} and {meta_path}: {reason}".format( | |
539 content_path=content_file_path, meta_path=meta_file_path, reason=e), error=True) | |
540 self.host.quit(1) | |
541 else: | |
542 self.disp(u"Blog item published") | |
543 | |
544 self.secureUnlink(sat_conf, content_file_path) | |
545 self.secureUnlink(sat_conf, meta_file_path) | |
546 | 395 |
547 def start(self): | 396 def start(self): |
548 command = self.args.item.lower() | |
549 sat_conf = config.parseMainConf() | |
550 # if there are user defined extension, we use them | 397 # if there are user defined extension, we use them |
551 SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {})) | 398 SYNTAX_EXT.update(config.getConfig(self.sat_conf, 'jp', CONF_SYNTAX_EXT, {})) |
552 current_syntax = None | 399 self.current_syntax = None |
553 pubsub_service = self.args.service | 400 |
554 pubsub_node = self.args.node | 401 self.pubsub_service, self.pubsub_node, content_file_path, content_file_obj, mb_data = self.getItemPath(self.args.item) |
555 pubsub_item = None | 402 |
556 | 403 self.edit(content_file_path, content_file_obj, mb_data=mb_data) |
557 if command not in ('new', 'last', 'current'): | |
558 # we have probably an URL, we try to parse it | |
559 import urlparse | |
560 url = self.args.item | |
561 parsed_url = urlparse.urlsplit(url) | |
562 if parsed_url.scheme.startswith('http'): | |
563 self.disp(u"{} URL found, trying to find associated xmpp: URI".format(parsed_url.scheme.upper()),1) | |
564 # HTTP URL, we try to find xmpp: links | |
565 try: | |
566 from lxml import etree | |
567 except ImportError: | |
568 self.disp(u"lxml module must be installed to use http(s) scheme, please install it with \"pip install lxml\"", error=True) | |
569 self.host.quit(1) | |
570 parser = etree.HTMLParser() | |
571 root = etree.parse(url, parser) | |
572 links = root.xpath("//link[@rel='alternate' and starts-with(@href, 'xmpp:')]") | |
573 if not links: | |
574 self.disp(u'Could not find alternate "xmpp:" URI, can\'t find associated XMPP PubSub node/item', error=True) | |
575 self.host.quit(1) | |
576 url = links[0].get('href') | |
577 parsed_url = urlparse.urlsplit(url) | |
578 | |
579 if parsed_url.scheme == 'xmpp': | |
580 if self.args.service or self.args.node: | |
581 self.parser.error(_(u"You can't use URI and --service or --node at the same time")) | |
582 | |
583 self.disp(u"XMPP URI used: {}".format(url),2) | |
584 # XXX: if we have not xmpp: URI here, we'll take the data as a file path | |
585 pubsub_service = parsed_url.path | |
586 pubsub_data = urlparse.parse_qs(parsed_url.query) | |
587 try: | |
588 pubsub_node = pubsub_data['node'][0] | |
589 except KeyError: | |
590 self.disp(u'No node found in xmpp: URI, can\'t retrieve item', error=True) | |
591 self.host.quit(1) | |
592 pubsub_item = pubsub_data.get('item',[None])[0] | |
593 if pubsub_item is not None: | |
594 command = 'edit' # XXX: edit command is only used internaly, it similar to last, but with the item given in the URL | |
595 else: | |
596 command = 'new' | |
597 | |
598 if command in ('new', 'last', 'edit'): | |
599 # we get current syntax to determine file extension | |
600 current_syntax = self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile) | |
601 # we now create a temporary file | |
602 tmp_suff = '.' + SYNTAX_EXT.get(current_syntax, SYNTAX_EXT['']) | |
603 content_file_obj, content_file_path = self.getTmpFile(sat_conf, tmp_suff) | |
604 if command == 'new': | |
605 self.disp(u'Editing a new blog item', 2) | |
606 mb_data = None | |
607 elif command in ('last', 'edit'): | |
608 self.disp(u'Editing requested published item', 2) | |
609 try: | |
610 items_ids = [pubsub_item] if pubsub_item is not None else [] | |
611 mb_data = self.host.bridge.mbGet(pubsub_service, pubsub_node, 1, items_ids, {}, self.profile)[0][0] | |
612 except Exception as e: | |
613 self.disp(u"Error while retrieving last item: {}".format(e)) | |
614 self.host.quit(1) | |
615 | |
616 content = mb_data['content_xhtml'] | |
617 if content and current_syntax != 'XHTML': | |
618 content = self.host.bridge.syntaxConvert(content, 'XHTML', current_syntax, False, self.profile) | |
619 content_file_obj.write(content.encode('utf-8')) | |
620 content_file_obj.seek(0) | |
621 else: | |
622 mb_data = None | |
623 if command == 'current': | |
624 # use wants to continue current draft | |
625 content_file_path = self.getCurrentFile(sat_conf, self.profile) | |
626 self.disp(u'Continuing edition of current draft', 2) | |
627 else: | |
628 # we consider the item as a file path | |
629 content_file_path = os.path.expanduser(self.args.item) | |
630 content_file_obj = open(content_file_path, 'r+b') | |
631 current_syntax = self.guessSyntaxFromPath(sat_conf, content_file_path) | |
632 | |
633 self.disp(u"Syntax used: {}".format(current_syntax), 1) | |
634 self.edit(sat_conf, content_file_path, content_file_obj, pubsub_service, pubsub_node, mb_data=mb_data) | |
635 | 404 |
636 | 405 |
637 class Preview(base.CommandBase, BlogCommon): | 406 class Preview(base.CommandBase, BlogCommon): |
638 | 407 |
639 def __init__(self, host): | 408 def __init__(self, host): |