diff --git a/api.py b/api.py index a3b5273..65b51fe 100644 --- a/api.py +++ b/api.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import requests +import asyncio from dataclasses import dataclass @dataclass class Story: @@ -20,15 +21,16 @@ def get_topstories(): raise Exception('Error fetching data from Hacker News API') return r.json() -def get_story(story_id): - story_url = f'https://hacker-news.firebaseio.com/v0/item/{story_id}.json' - s = requests.get(story_url).json() - return Story(s['id'], - s['title'], - s['url'] if 'url' in s else 'No URL', - s['by'], - s['score'], - len(s['kids']) if 'kids' in s else 0, False) +async def get_story(session, story_id): + #story_url = f'https://hacker-news.firebaseio.com/v0/item/{story_id}.json' + async with session.get(f'https://hacker-news.firebaseio.com/v0/item/{story_id}.json') as resp: + s = await resp.json() #requests.get(story_url).json() + return Story(s['id'], + s['title'], + s['url'] if 'url' in s else 'No URL', + s['by'], + s['score'], + len(s['kids']) if 'kids' in s else 0, False) def main(): ts = get_topstories() diff --git a/hn.py b/hn.py index 3b911dc..71ea4d9 100755 --- a/hn.py +++ b/hn.py @@ -6,6 +6,9 @@ import curses import webbrowser import math +import aiohttp +import asyncio + import api spinner_states = ['-', '\\', '|', '/'] @@ -30,19 +33,6 @@ class Client: self.cols = curses.COLS self.stories_in_a_site = self.lines - 3 - def load_stories(self, from_pos, to_pos): - for idx, i in enumerate(self.topstories[from_pos:to_pos]): - self.set_footer(f'[{spinner_states[idx%4]}] Loading { to_pos - from_pos } stories...') - self.screen.refresh() - if i in self.loadedstories.keys(): - # upon reloading, refresh counts + title - updated_story = api.get_story(i) - self.loadedstories[i].votes = updated_story.votes - self.loadedstories[i].comments = updated_story.comments - self.loadedstories[i].title = updated_story.title - pass - else: - self.loadedstories[i] = api.get_story(i) def set_footer(self, footer): self.screen.addstr(curses.LINES - 1, 0, footer, curses.A_REVERSE) @@ -62,6 +52,7 @@ class Client: for i, story_id in enumerate(self.topstories[self.story_pos:self.story_pos + self.stories_in_a_site]): story = self.loadedstories[story_id] + prefix = '>' if i == self.cursor_pos else '' # calculate length of line text = f'{prefix} ()\n' @@ -92,8 +83,9 @@ class Client: page = int(self.story_pos / self.stories_in_a_site + 1) total_pages = math.ceil(len(self.loadedstories)/500) self.set_footer(f'Page {page}/{total_pages}, loaded {len(self.loadedstories)} stories.') + #self.set_footer(f'{self.loadedstories}') - def handle_input(self): + async def handle_input(self): c = self.screen.getch() story = self.loadedstories[self.topstories[self.story_pos + self.cursor_pos]] if c == ord('q'): # Quit @@ -112,7 +104,7 @@ class Client: self.cursor_pos = 0 # scroll up down a page :) self.story_pos += self.stories_in_a_site - self.load_more_if_needed() + await self.load_more_if_needed() elif c == ord('c'): # open comments @@ -131,39 +123,72 @@ class Client: curses.resize_term(*self.screen.getmaxyx()) self.lines, self.cols = self.screen.getmaxyx() self.stories_in_a_site = self.lines - 3 - self.load_more_if_needed() + with open('log', 'a') as f: + f.write(f'resizing!!!') + await self.load_more_if_needed() + - def load_more_if_needed(self): + async def load_more_if_needed(self): + + with open('log', 'a') as f: + f.write(f'len: {len(self.loadedstories)} res: { self.story_pos + self.stories_in_a_site}\n') if len(self.loadedstories) < self.story_pos + self.stories_in_a_site: # load more - self.load_stories(self.story_pos, self.story_pos + self.stories_in_a_site) + with open('log', 'a') as f: + f.write('loading more...\n') + await self.load_stories(self.story_pos, self.story_pos + self.stories_in_a_site) + + async def load_stories(self, from_pos, to_pos): + #self.set_footer(f'[{spinner_states[idx%4]}] Loading { to_pos - from_pos } stories...') + self.set_footer(f'Loading stories...') + self.screen.refresh() + story_list = [] + tasks = [] + #async with self.session as session: + session = self.session + for idx, i in enumerate(self.topstories[from_pos:to_pos]): + tasks.append(asyncio.ensure_future(api.get_story(session, i))) + story_list = await asyncio.gather(*tasks) + for story in story_list: + if story.id in self.loadedstories.keys(): + # upon reloading, refresh counts + title + self.loadedstories[story.id].votes = story.votes + self.loadedstories[story.id].comments = story.comments + self.loadedstories[story.id].title = story.title + pass + else: + self.loadedstories[story.id] = story - def run(self): - self.load_stories(0, self.stories_in_a_site) + async def run(self): + self.session = aiohttp.ClientSession() + await self.load_stories(0, self.stories_in_a_site) while True: self.draw() - self.handle_input() + await self.handle_input() def exit(self): + self.session.close() curses.endwin() import sys sys.exit(0) - def reload(self): + async def reload(self): self.set_footer("Reloading...") self.screen.refresh() self.topstories = api.get_topstories() #self.loadedstories = [] self.story_pos = 0 self.cursor_pos = 0 - self.load_stories(self.cursor_pos, self.cursor_pos + self.stories_in_a_site) + await self.load_stories(self.cursor_pos, self.cursor_pos + self.stories_in_a_site) def main(): try: client = Client() - client.run() + #async with client.run(): + asyncio.run(client.run()) except Exception as e: curses.endwin() + client.session.close() raise e if __name__ == '__main__':