diff --git a/rtv/exceptions.py b/rtv/exceptions.py index 3e8fa6a..1d9f976 100644 --- a/rtv/exceptions.py +++ b/rtv/exceptions.py @@ -35,4 +35,8 @@ class ProgramError(RTVError): class BrowserError(RTVError): - "Could not open a web browser tab" \ No newline at end of file + "Could not open a web browser tab" + + +class TemporaryFileError(RTVError): + "Indicates that an error has occurred and the file should not be deleted" \ No newline at end of file diff --git a/rtv/page.py b/rtv/page.py index a5c8184..fbbbf66 100644 --- a/rtv/page.py +++ b/rtv/page.py @@ -10,6 +10,7 @@ from kitchen.text.display import textual_width from . import docs from .objects import Controller, Color, Command +from .exceptions import TemporaryFileError def logged_in(f): @@ -217,16 +218,20 @@ class Page(object): self.term.flash() return - text = self.term.open_editor(info) - if text == content: - self.term.show_notification('Canceled') - return + with self.term.open_editor(info) as text: + if text == content: + self.term.show_notification('Canceled') + return + + with self.term.loader('Editing', delay=0): + data['object'].edit(text) + time.sleep(2.0) + + if self.term.loader.exception is None: + self.refresh_content() + else: + raise TemporaryFileError() - with self.term.loader('Editing', delay=0): - data['object'].edit(text) - time.sleep(2.0) - if self.term.loader.exception is None: - self.refresh_content() @PageController.register(Command('INBOX')) @logged_in diff --git a/rtv/submission.py b/rtv/submission.py index fd4c954..a70dd3a 100644 --- a/rtv/submission.py +++ b/rtv/submission.py @@ -8,6 +8,7 @@ from . import docs from .content import SubmissionContent from .page import Page, PageController, logged_in from .objects import Navigator, Color, Command +from .exceptions import TemporaryFileError class SubmissionController(PageController): @@ -121,17 +122,20 @@ class SubmissionPage(Page): type=data['type'].lower(), content=content) - comment = self.term.open_editor(comment_info) - if not comment: - self.term.show_notification('Canceled') - return + with self.term.open_editor(comment_info) as comment: + if not comment: + self.term.show_notification('Canceled') + return - with self.term.loader('Posting', delay=0): - reply(comment) - # Give reddit time to process the submission - time.sleep(2.0) - if not self.term.loader.exception: - self.refresh_content() + with self.term.loader('Posting', delay=0): + reply(comment) + # Give reddit time to process the submission + time.sleep(2.0) + + if self.term.loader.exception is None: + self.refresh_content() + else: + raise TemporaryFileError() @SubmissionController.register(Command('DELETE')) @logged_in diff --git a/rtv/subreddit.py b/rtv/subreddit.py index 4c85fe5..21b87b6 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -10,6 +10,7 @@ from .page import Page, PageController, logged_in from .objects import Navigator, Color, Command from .submission import SubmissionPage from .subscription import SubscriptionPage +from .exceptions import TemporaryFileError class SubredditController(PageController): @@ -118,31 +119,31 @@ class SubredditPage(Page): return submission_info = docs.SUBMISSION_FILE.format(name=name) - text = self.term.open_editor(submission_info) - if not text or '\n' not in text: - self.term.show_notification('Canceled') - return + with self.term.open_editor(submission_info) as text: + if not text or '\n' not in text: + self.term.show_notification('Canceled') + return - title, content = text.split('\n', 1) - with self.term.loader('Posting', delay=0): - submission = self.reddit.submit(name, title, text=content, - raise_captcha_exception=True) - # Give reddit time to process the submission - time.sleep(2.0) - if self.term.loader.exception: - return + title, content = text.split('\n', 1) + with self.term.loader('Posting', delay=0): + submission = self.reddit.submit(name, title, text=content, + raise_captcha_exception=True) + # Give reddit time to process the submission + time.sleep(2.0) + if self.term.loader.exception: + raise TemporaryFileError() - # Open the newly created post - with self.term.loader('Loading submission'): - page = SubmissionPage( - self.reddit, self.term, self.config, self.oauth, - submission=submission) - if self.term.loader.exception: - return + # Open the newly created post + with self.term.loader('Loading submission'): + page = SubmissionPage( + self.reddit, self.term, self.config, self.oauth, + submission=submission) + if self.term.loader.exception: + return - page.loop() + page.loop() - self.refresh_content() + self.refresh_content() @SubredditController.register(Command('SUBREDDIT_OPEN_SUBSCRIPTIONS')) @logged_in diff --git a/rtv/terminal.py b/rtv/terminal.py index 3d2f784..70bc2f0 100644 --- a/rtv/terminal.py +++ b/rtv/terminal.py @@ -6,12 +6,14 @@ import sys import time import codecs import curses +import logging +import tempfile import webbrowser import subprocess import curses.ascii from curses import textpad +from datetime import datetime from contextlib import contextmanager -from tempfile import NamedTemporaryFile import six from kitchen.text.display import textual_width_chop @@ -27,6 +29,9 @@ except ImportError: unescape = html_parser.HTMLParser().unescape +_logger = logging.getLogger(__name__) + + class Terminal(object): MIN_HEIGHT = 10 @@ -377,37 +382,54 @@ class Terminal(object): except OSError: self.show_notification('Could not open pager %s' % pager) + @contextmanager def open_editor(self, data=''): """ Open a temporary file using the system's default editor. The data string will be written to the file before opening. This function will block until the editor has closed. At that point the file - will be read and and lines starting with '#' will be stripped. + will be read and and lines starting with '#' will be stripped. If no + errors occur, the file will be deleted when the context manager closes. """ - with NamedTemporaryFile(prefix='rtv-', suffix='.txt', mode='wb') as fp: + filename = 'rtv_{:%Y%m%d_%H%M%S}.txt'.format(datetime.now()) + filepath = os.path.join(tempfile.gettempdir(), filename) + + with codecs.open(filepath, 'w', 'utf-8') as fp: fp.write(self.clean(data)) - fp.flush() - editor = os.getenv('RTV_EDITOR') or os.getenv('EDITOR') or 'nano' + _logger.info('File created: {}'.format(filepath)) + editor = os.getenv('RTV_EDITOR') or os.getenv('EDITOR') or 'nano' + try: + with self.suspend(): + p = subprocess.Popen([editor, filepath]) + try: + p.wait() + except KeyboardInterrupt: + p.terminate() + except OSError: + self.show_notification('Could not open file with %s' % editor) + + with codecs.open(filepath, 'r', 'utf-8') as fp: + text = ''.join(line for line in fp if not line.startswith('#')) + text = text.rstrip() + + try: + yield text + except exceptions.TemporaryFileError: + # All exceptions will cause the file to *not* be removed, but these + # ones should also be swallowed + _logger.info('Caught TemporaryFileError') + self.show_notification('File saved as: {}'.format(text)) + else: + # If no errors occurred, try to remove the file try: - with self.suspend(): - p = subprocess.Popen([editor, fp.name]) - try: - p.wait() - except KeyboardInterrupt: - p.terminate() - except OSError: - self.show_notification('Could not open file with %s' % editor) - - # Open a second file object to read. This appears to be necessary - # in order to read the changes made by some editors (gedit). w+ - # mode does not work! - with codecs.open(fp.name, 'r', 'utf-8') as fp2: - text = ''.join(line for line in fp2 if not line.startswith('#')) - text = text.rstrip() - return text + os.remove(filepath) + except OSError as e: + _logger.exception(e) + else: + _logger.info('File deleted: {}'.format(filepath)) def text_input(self, window, allow_resize=False): """ @@ -545,4 +567,4 @@ class Terminal(object): break out = '\n'.join(stack) - return out + return out \ No newline at end of file