diff --git a/rtv/__main__.py b/rtv/__main__.py index af50d7d..733b02b 100644 --- a/rtv/__main__.py +++ b/rtv/__main__.py @@ -10,10 +10,11 @@ import praw.errors from six.moves import configparser from . import config -from .exceptions import SubmissionError, SubredditError, ProgramError +from .exceptions import SubmissionError, SubredditError, SubscriptionError, ProgramError from .curses_helpers import curses_session from .submission import SubmissionPage from .subreddit import SubredditPage +from .subscriptions import SubscriptionPage from .docs import * from .__version__ import __version__ diff --git a/rtv/content.py b/rtv/content.py index c0d397f..f4031da 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -4,10 +4,10 @@ import praw import requests import re -from .exceptions import SubmissionError, SubredditError, AccountError +from .exceptions import SubmissionError, SubredditError, SubscriptionError, AccountError from .helpers import humanize_timestamp, wrap_text, strip_subreddit_url -__all__ = ['SubredditContent', 'SubmissionContent'] +__all__ = ['SubredditContent', 'SubmissionContent', 'SubscriptionContent'] _logger = logging.getLogger(__name__) @@ -143,6 +143,20 @@ class BaseContent(object): return data + @staticmethod + def strip_praw_subscription(subscription): + """ + Parse through a subscription and return a dict with data ready to be + displayed through the terminal. + """ + + data = {} + data['object'] = subscription + data['type'] = 'Subscription' + data['name'] = subscription._case_name + data['title'] = subscription.title + + return data class SubmissionContent(BaseContent): """ @@ -352,3 +366,47 @@ class SubredditContent(BaseContent): data['offset'] = 0 return data + +class SubscriptionContent(BaseContent): + def __init__(self, subscriptions, loader): + self.name = "Subscriptions" + self._loader = loader + self._subscriptions = subscriptions + self._subscription_data = [] + + @classmethod + def get_list(cls, reddit, loader): + try: + with loader(): + subscriptions = reddit.get_my_subreddits(limit=None) + except praw.errors.APIException: + raise SubscriptionError() + + return cls(subscriptions, loader) + + def get(self, index, n_cols=70): + """ + Grab the `i`th subscription, with the title field formatted to fit inside + of a window of width `n_cols` + """ + + if index < 0: + raise IndexError + + while index >= len(self._subscription_data): + try: + with self._loader(): + subscription = next(self._subscriptions) + except StopIteration: + raise IndexError + else: + data = self.strip_praw_subscription(subscription) + self._subscription_data.append(data) + + data = self._subscription_data[index] + subreddit_info = "/r/" + data['name'] + " - " + data['title'] + data['split_title'] = wrap_text(subreddit_info, width=n_cols) + data['n_rows'] = len(data['split_title']) + 3 + data['offset'] = 0 + + return data diff --git a/rtv/exceptions.py b/rtv/exceptions.py index dece7fe..80fb76f 100644 --- a/rtv/exceptions.py +++ b/rtv/exceptions.py @@ -24,6 +24,10 @@ class SubredditError(RTVError): self.name = name +class SubscriptionError(RTVError): + "Subscriptions could not be fetched" + + class ProgramError(RTVError): "Problem executing an external program" diff --git a/rtv/page.py b/rtv/page.py index cc4d7b4..686f5d4 100644 --- a/rtv/page.py +++ b/rtv/page.py @@ -412,6 +412,22 @@ class BasePage(object): s.catch = False self.refresh_content() + @BaseController.register('s') + def get_subscriptions(self): + """ + Displays subscribed subreddits + """ + + if not self.reddit.is_logged_in(): + show_notification(self.stdscr, ['Not logged in']) + return + + data = self.content.get(self.nav.absolute_index) + with self.safe_call as s: + subscriptions = SubscriptionPage(self.stdscr, self.reddit) + subscriptions.loop() + self.refresh_content() + @BaseController.register('i') def get_inbox(self): """ diff --git a/rtv/subscriptions.py b/rtv/subscriptions.py new file mode 100644 index 0000000..5d9cf02 --- /dev/null +++ b/rtv/subscriptions.py @@ -0,0 +1,63 @@ +import curses +import sys +import time +import logging + +from .content import SubscriptionContent +from .page import BasePage, Navigator, BaseController +from .helpers import open_browser, open_editor +from .curses_helpers import (Color, LoadScreen, get_arrow, get_gold, add_line, + show_notification) +from .docs import COMMENT_FILE + +__all__ = ['SubscriptionController', 'SubscriptionPage'] +_logger = logging.getLogger(__name__) + +class SubscriptionController(BaseController): + character_map = {} + +class SubscriptionPage(BasePage): + def __init__(self, stdscr, reddit): + self.controller = SubscriptionController(self) + self.loader = LoadScreen(stdscr) + + content = SubscriptionContent.get_list(reddit, self.loader) + super(SubscriptionPage, self).__init__(stdscr, reddit, content) + + def loop(self): + "Main control loop" + + self.active = True + while self.active: + self.draw() + cmd = self.stdscr.getch() + self.controller.trigger(cmd) + + @SubscriptionController.register(curses.KEY_F5, 'r') + def refresh_content(self): + "Re-download all subscriptions and reset the page index" + + try: + self.content = SubscriptionContent.get_list(self.reddit, self.loader) + except AccountError: + show_notification(self.stdscr, ['Not logged in']) + except SubredditError: + show_notification(self.stdscr, ['Invalid subreddit']) + except requests.HTTPError: + show_notification(self.stdscr, ['Could not reach subreddit']) + else: + self.nav = Navigator(self.content.get) + + @staticmethod + def draw_item(win, data, inverted=False): + n_rows, n_cols = win.getmaxyx() + n_cols -= 1 # Leave space for the cursor in the first column + + # Handle the case where the window is not large enough to fit the data. + valid_rows = range(0, n_rows) + offset = 0 if not inverted else -(data['n_rows'] - n_rows) + + n_title = len(data['split_title']) + for row, text in enumerate(data['split_title'], start=offset): + if row in valid_rows: + add_line(win, text, row, 1, curses.A_BOLD)