def __init__(self, stdscr=None): if Main.instance: raise Exception("attempting to create a duplicate Main instance") Main.instance = self signal.signal(signal.SIGUSR2, self.debug_out) # Let locale figure itself out locale.setlocale(locale.LC_ALL, "") enc = locale.getpreferredencoding() # If we're canto-fetch, jump to that main function if sys.argv[0].endswith("canto-fetch"): canto_fetch.main(enc) # Parse arguments that canto shares with canto-fetch, return # a lot of file locations and an optlist that will contain the # parsed, but yet unused canto specific arguments. conf_dir, log_file, conf_file, feed_dir, script_dir, optlist = args.parse_common_args( enc, "hvulaor:t:i:n:", ["help", "version", "update", "list", "checkall", "opml", "import=", "url=", "checknew=", "tag="], ) # Instantiate the config and start the log. try: self.cfg = get_cfg(conf_file, log_file, feed_dir, script_dir) self.cfg.parse() except: traceback.print_exc() upgrade_help() sys.exit(-1) self.cfg.log("Canto v %s (%s)" % ("%d.%d.%d" % VERSION_TUPLE, GIT_SHA), "w") self.cfg.log("Time: %s" % time.asctime()) self.cfg.log("Config parsed successfully.") # If we were passed an existing curses screen (i.e. restart) # pass it through to the config. self.cfg.stdscr = stdscr if self.cfg.stdscr: self.restarting = True else: self.restarting = False self.restart = False # Default arguments. flags = 0 feed_ct = None opml_file = None url = None newtag = None # Note that every single flag that takes an argument has its # argument converted to unicode. Saves a lot of bullshit later. for opt, arg in optlist: if opt in ["-u", "--update"]: flags |= UPDATE_FIRST elif opt in ["-n", "--checknew"]: flags |= CHECK_NEW feed_ct = unicode(arg, enc, "ignore") elif opt in ["-a", "--checkall"]: flags |= CHECK_NEW elif opt in ["-l", "--list"]: flags |= FEED_LIST elif opt in ["-o", "--opml"]: flags |= OUT_OPML elif opt in ["-i", "--import"]: flags |= IN_OPML opml_file = unicode(arg, enc, "ignore") elif opt in ["-r", "--url"]: flags |= IN_URL url = unicode(arg, enc, "ignore") elif opt in ["-t", "--tag"]: newtag = unicode(arg, enc, "ignore") # Import flags harness the same functions as their config # based counterparts, source_opml and source_url. if flags & IN_OPML: self.cfg.locals["source_opml"](opml_file, append=True) print "OPML imported." if flags & IN_URL: self.cfg.locals["source_url"](url, append=True, tag=newtag) print "URL added." # All import options should terminate. if flags & (IN_OPML + IN_URL): sys.exit(0) # If self.cfg had to generate a config, make sure we # update first. if self.cfg.no_conf: self.cfg.log("Conf was auto-generated, adding -u") flags |= UPDATE_FIRST if flags & UPDATE_FIRST: self.cfg.log("Pausing to update...") canto_fetch.run(self.cfg, True, True) # Detect if there are any new feeds by whether their # set path exists. If not, run canto-fetch but don't # force it, so canto-fetch intelligently updates. for i, f in enumerate(self.cfg.feeds): if not os.path.exists(f.path): self.cfg.log("Detected unfetched feed: %s." % f.URL) canto_fetch.run(self.cfg, True, False) # Still no go? if not os.path.exists(f.path): self.cfg.log("Failed to fetch %s, removing" % f.URL) self.cfg.feeds[i] = None else: self.cfg.log("Fetched.\n") break # Collapse the feed array, if we had to remove some unfetchables. self.cfg.feeds = [f for f in self.cfg.feeds if f is not None] self.new = [] self.old = [] self.ph = ProcessHandler(self.cfg) # Force an update from disk by queueing a work item for each thread. # At this point, we just want to do the portion of the update where the # disk is read, so PROC_UPDATE is used. self.cfg.log("Populating feeds...") for f in self.cfg.feeds: self.ph.send((PROC_UPDATE, f.URL, [])) for f in self.cfg.feeds: f.merge(self.ph.recv()[1]) self.ph.send((PROC_GETTAGS,)) fixedtags = self.ph.recv() self.ph.kill_process() for i, f in enumerate(self.cfg.feeds): self.cfg.feeds[i].tags = fixedtags[i] # Now that the tags have all been straightened out, validate the config. # Making sure the tags are unique before validation is important because # part of validation is the actual creation of Tag() objects. try: self.cfg.validate() except Exception, err: print err upgrade_help() sys.exit(0)
def __init__(self, stdscr=None): signal.signal(signal.SIGUSR2, self.debug_out) # Let locale figure itself out locale.setlocale(locale.LC_ALL, "") enc = locale.getpreferredencoding() # If we're canto-fetch, jump to that main function if sys.argv[0].endswith("canto-fetch"): canto_fetch.main(enc) # Parse arguments that canto shares with canto-fetch, return # a lot of file locations and an optlist that will contain the # parsed, but yet unused canto specific arguments. conf_dir, log_file, conf_file, feed_dir, script_dir, optlist =\ args.parse_common_args(enc, "hvulaor:t:i:n:", ["help","version","update","list","checkall","opml", "import=","url=","checknew=","tag="]) # Instantiate the config and start the log. try: self.cfg = get_cfg(conf_file, log_file, feed_dir, script_dir) self.cfg.parse() except: traceback.print_exc() upgrade_help() sys.exit(-1) self.cfg.log("Canto v %s (%s)" % \ ("%d.%d.%d" % VERSION_TUPLE, GIT_SHA), "w") self.cfg.log("Time: %s" % time.asctime()) self.cfg.log("Config parsed successfully.") # If we were passed an existing curses screen (i.e. restart) # pass it through to the config. self.cfg.stdscr = stdscr if self.cfg.stdscr: self.restarting = True else: self.restarting = False self.restart = False # Default arguments. flags = 0 feed_ct = None opml_file = None url = None newtag = None # Note that every single flag that takes an argument has its # argument converted to unicode. Saves a lot of bullshit later. for opt, arg in optlist: if opt in ["-u", "--update"]: flags |= UPDATE_FIRST elif opt in ["-n", "--checknew"]: flags |= CHECK_NEW feed_ct = unicode(arg, enc, "ignore") elif opt in ["-a", "--checkall"]: flags |= CHECK_NEW elif opt in ["-l", "--list"]: flags |= FEED_LIST elif opt in ["-o", "--opml"]: flags |= OUT_OPML elif opt in ["-i", "--import"]: flags |= IN_OPML opml_file = unicode(arg, enc, "ignore") elif opt in ["-r", "--url"]: flags |= IN_URL url = unicode(arg, enc, "ignore") elif opt in ["-t", "--tag"]: newtag = unicode(arg, enc, "ignore") # Import flags harness the same functions as their config # based counterparts, source_opml and source_url. if flags & IN_OPML: self.cfg.locals['source_opml'](opml_file, append=True) print "OPML imported." if flags & IN_URL: self.cfg.locals['source_url'](url, append=True, tag=newtag) print "URL added." # All import options should terminate. if flags & (IN_OPML + IN_URL): sys.exit(0) # If self.cfg had to generate a config, make sure we # update first. if self.cfg.no_conf: self.cfg.log("Conf was auto-generated, adding -u") flags |= UPDATE_FIRST if flags & UPDATE_FIRST: self.cfg.log("Pausing to update...") canto_fetch.run(self.cfg, True, True) # Detect if there are any new feeds by whether their # set path exists. If not, run canto-fetch but don't # force it, so canto-fetch intelligently updates. for i, f in enumerate(self.cfg.feeds): if not os.path.exists(f.path): self.cfg.log("Detected unfetched feed: %s." % f.URL) canto_fetch.run(self.cfg, True, False) #Still no go? if not os.path.exists(f.path): self.cfg.log("Failed to fetch %s, removing" % f.URL) self.cfg.feeds[i] = None else: self.cfg.log("Fetched.\n") break # Collapse the feed array, if we had to remove some unfetchables. self.cfg.feeds = filter(lambda x: x != None, self.cfg.feeds) self.new = [] self.old = [] self.ph = ProcessHandler(self.cfg) # Force an update from disk by queueing a work item for each thread. # At this point, we just want to do the portion of the update where the # disk is read, so PROC_UPDATE is used. self.cfg.log("Populating feeds...") for f in self.cfg.feeds: self.ph.send((PROC_UPDATE, f.URL, [])) for f in self.cfg.feeds: f.merge(self.ph.recv()[1]) self.ph.send((PROC_GETTAGS, )) fixedtags = self.ph.recv() self.ph.kill_process() for i, f in enumerate(self.cfg.feeds): self.cfg.feeds[i].tags = fixedtags[i] # Now that the tags have all been straightened out, validate the config. # Making sure the tags are unique before validation is important because # part of validation is the actual creation of Tag() objects. try: self.cfg.validate() except Exception, err: print err upgrade_help() sys.exit(0)
class Main: instance = None def __init__(self, stdscr=None): if Main.instance: raise Exception("attempting to create a duplicate Main instance") Main.instance = self signal.signal(signal.SIGUSR2, self.debug_out) # Let locale figure itself out locale.setlocale(locale.LC_ALL, "") enc = locale.getpreferredencoding() # If we're canto-fetch, jump to that main function if sys.argv[0].endswith("canto-fetch"): canto_fetch.main(enc) # Parse arguments that canto shares with canto-fetch, return # a lot of file locations and an optlist that will contain the # parsed, but yet unused canto specific arguments. conf_dir, log_file, conf_file, feed_dir, script_dir, optlist = args.parse_common_args( enc, "hvulaor:t:i:n:", ["help", "version", "update", "list", "checkall", "opml", "import=", "url=", "checknew=", "tag="], ) # Instantiate the config and start the log. try: self.cfg = get_cfg(conf_file, log_file, feed_dir, script_dir) self.cfg.parse() except: traceback.print_exc() upgrade_help() sys.exit(-1) self.cfg.log("Canto v %s (%s)" % ("%d.%d.%d" % VERSION_TUPLE, GIT_SHA), "w") self.cfg.log("Time: %s" % time.asctime()) self.cfg.log("Config parsed successfully.") # If we were passed an existing curses screen (i.e. restart) # pass it through to the config. self.cfg.stdscr = stdscr if self.cfg.stdscr: self.restarting = True else: self.restarting = False self.restart = False # Default arguments. flags = 0 feed_ct = None opml_file = None url = None newtag = None # Note that every single flag that takes an argument has its # argument converted to unicode. Saves a lot of bullshit later. for opt, arg in optlist: if opt in ["-u", "--update"]: flags |= UPDATE_FIRST elif opt in ["-n", "--checknew"]: flags |= CHECK_NEW feed_ct = unicode(arg, enc, "ignore") elif opt in ["-a", "--checkall"]: flags |= CHECK_NEW elif opt in ["-l", "--list"]: flags |= FEED_LIST elif opt in ["-o", "--opml"]: flags |= OUT_OPML elif opt in ["-i", "--import"]: flags |= IN_OPML opml_file = unicode(arg, enc, "ignore") elif opt in ["-r", "--url"]: flags |= IN_URL url = unicode(arg, enc, "ignore") elif opt in ["-t", "--tag"]: newtag = unicode(arg, enc, "ignore") # Import flags harness the same functions as their config # based counterparts, source_opml and source_url. if flags & IN_OPML: self.cfg.locals["source_opml"](opml_file, append=True) print "OPML imported." if flags & IN_URL: self.cfg.locals["source_url"](url, append=True, tag=newtag) print "URL added." # All import options should terminate. if flags & (IN_OPML + IN_URL): sys.exit(0) # If self.cfg had to generate a config, make sure we # update first. if self.cfg.no_conf: self.cfg.log("Conf was auto-generated, adding -u") flags |= UPDATE_FIRST if flags & UPDATE_FIRST: self.cfg.log("Pausing to update...") canto_fetch.run(self.cfg, True, True) # Detect if there are any new feeds by whether their # set path exists. If not, run canto-fetch but don't # force it, so canto-fetch intelligently updates. for i, f in enumerate(self.cfg.feeds): if not os.path.exists(f.path): self.cfg.log("Detected unfetched feed: %s." % f.URL) canto_fetch.run(self.cfg, True, False) # Still no go? if not os.path.exists(f.path): self.cfg.log("Failed to fetch %s, removing" % f.URL) self.cfg.feeds[i] = None else: self.cfg.log("Fetched.\n") break # Collapse the feed array, if we had to remove some unfetchables. self.cfg.feeds = [f for f in self.cfg.feeds if f is not None] self.new = [] self.old = [] self.ph = ProcessHandler(self.cfg) # Force an update from disk by queueing a work item for each thread. # At this point, we just want to do the portion of the update where the # disk is read, so PROC_UPDATE is used. self.cfg.log("Populating feeds...") for f in self.cfg.feeds: self.ph.send((PROC_UPDATE, f.URL, [])) for f in self.cfg.feeds: f.merge(self.ph.recv()[1]) self.ph.send((PROC_GETTAGS,)) fixedtags = self.ph.recv() self.ph.kill_process() for i, f in enumerate(self.cfg.feeds): self.cfg.feeds[i].tags = fixedtags[i] # Now that the tags have all been straightened out, validate the config. # Making sure the tags are unique before validation is important because # part of validation is the actual creation of Tag() objects. try: self.cfg.validate() except Exception, err: print err upgrade_help() sys.exit(0) # Print out a feed list if flags & FEED_LIST: for f in self.cfg.feeds: print f.tags[0].encode(enc, "ignore") sys.exit(0) # This could probably be done better, or more officially # with some XML library, but for now, print is working # fairly well. if flags & OUT_OPML: self.cfg.log("Outputting OPML") print """<opml version="1.0">""" print """<body>""" for feed in self.cfg.feeds: ufp = feed.get_ufp() if "atom" in ufp["version"]: t = "pie" else: t = "rss" print """\t<outline text="%s" xmlUrl="%s" type="%s" />""" % ( feed.tags[0].encode(enc, "ignore"), feed.URL, t, ) print """</body>""" print """</opml>""" sys.exit(0) # Handle -a/-n flags (print number of new items) if flags & CHECK_NEW: if not feed_ct: unread = 0 for f in self.cfg.feeds: for s in f: if "read" not in s["canto_state"]: unread += 1 print unread else: t = [f for f in self.cfg.feeds if f.tags[0] == feed_ct] if not t: print "Unknown Feed" else: print len([x for x in t[0] if "read" not in x["canto_state"]]) sys.exit(0) # After this point, we know that all further operation is going to # require all tags to be populated, we queue up the latter # half of the work, to actually filter and sort the items. # The reason we clear the feeds first is that only after validation do # we know what keys are going to have to be precached, and canto tries # hard to conserve items (see feed.merge), so we need to replace all of # them with corrected, fresh items from the process that knows about the # precache for f in self.cfg.feeds: del f[:] signal.signal(signal.SIGCHLD, self.chld) self.update(1, self.cfg.feeds, PROC_BOTH) # At this point we know that we're going to actually launch # the client, so we fire up ncurses and add the screen # information to our Cfg(). if not self.restarting: self.cfg.stdscr = curses.initscr() self.cfg.stdscr.nodelay(1) # curs_set can return ERR, we shouldn't care try: curses.curs_set(0) except: pass # if any of these mess up though, the rest of the # the operation is suspect, so die. try: curses.noecho() curses.start_color() curses.use_default_colors() except: self.cfg.log("Unable to init curses, bailing") self.done() self.sigusr = 0 self.resize = 0 self.alarmed = 0 self.ticks = 60 self.cfg.height, self.cfg.width = self.cfg.stdscr.getmaxyx() # Init colors try: for i, (fg, bg) in enumerate(self.cfg.colors): curses.init_pair(i + 1, fg, bg) except: self.cfg.log("Unable to init curses color pairs!") self.cfg.log("Curses initialized.") # Instantiate the base Gui class self.gui = Gui(self.cfg, self.cfg.tags.cur()) self.cfg.log("GUI initialized.") # Signal handling signal.signal(signal.SIGWINCH, self.winch) signal.signal(signal.SIGALRM, self.alarm) signal.signal(signal.SIGINT, self.done) signal.signal(signal.SIGUSR1, self.sigusr) signal.alarm(1) self.cfg.log("Signals set.") self.estring = None # The main loop is wrapped in one huge try...except so that no matter # what exception is thrown, we can clean up curses and exit without # s******g all over the terminal. try: # Initial draw of the screen, if not restarting self.refresh(self.restarting) self.restarting = False # Main program loop, terminated when all handlers have # deregistered / exited. self.cfg.log("Beginning main loop.") while 1: # Clear the key t = None # Gui is none if a key returned EXIT if not self.gui: break # If we've spawned a text-based process (i.e. elinks), then just # pause and wait to be awakened by SIGCHLD if self.cfg.wait_for_pid: signal.pause() # Tick when SIGALRM is received. if self.alarmed: self.alarmed = 0 self.tick() # Deferred update from signal if self.sigusr: self.sigusr = 0 if "signal" in self.cfg.triggers: self.update() # Get the key k = self.cfg.stdscr.getch() # KEY_RESIZE is the only key not propagated, to # keep users from rebinding it and crashing. if k == curses.KEY_RESIZE or self.resize: self.resize = 0 self.refresh() continue # No input, time to check on the worker. if k == -1: # Make sure we don't pin the CPU, so if there's no input and # no waiting updates, sleep for awhile. if not self.ph.pid: time.sleep(1 / 60.0) continue r = self.ph.recv(True, 0.01) if r: feed = [f for f in self.cfg.feeds if f.URL == r[1]][0] if r[0] == PROC_UPDATE: old = [] for gf, tf, s, l in r[4]: if not l: old.append((gf, tf, s, l)) continue for i, oldidx in enumerate(l): l[i] = feed[oldidx] old.append((gf, tf, s, l)) feed.merge(r[2]) new = [] for gf, tf, s, l in r[3]: if not l: new.append((gf, tf, s, None)) continue for i, newidx in enumerate(l): l[i] = (feed[newidx], newidx) new.append((gf, tf, s, l)) self.gui.alarm(new, old) self.gui.draw_elements() feed.qd = False continue # Handle Meta pairs elif k == 195: k2 = self.cfg.stdscr.getch() if k2 >= 64: t = (k2 - 64, 1) else: t = (k, 0) # Just a normal key-press else: t = (k, 0) # Key resolves a keypress tuple into a list of actions actions = self.gui.key(t) # Actions are executed in order, and each return code # is handled in order. for a in actions: r = self.gui.action(a) if r == REFRESH_ALL: self.refresh() elif r == UPDATE: self.update() elif r in [REFILTER, RETAG]: self.ph.flush() self.update(1) elif r == TFILTER: # Tag filters shouldn't perform a full update, so we map # the relevant tag to all of the feeds that include that # tag and update them. t = self.gui.sel["tag"] ufds = [f for f in self.cfg.feeds if t.tag in f.tags] t.clear() self.update(1, ufds) elif r == REDRAW_ALL: self.gui.draw_elements() elif r == RESTART: self.restart = True self.gui = None elif r == EXIT: self.gui = None break time.sleep(0.01) except Exception: self.estring = traceback.format_exc() except KeyboardInterrupt: pass self.done()
class Main(): def __init__(self, stdscr=None): signal.signal(signal.SIGUSR2, self.debug_out) # Let locale figure itself out locale.setlocale(locale.LC_ALL, "") enc = locale.getpreferredencoding() # If we're canto-fetch, jump to that main function if sys.argv[0].endswith("canto-fetch"): canto_fetch.main(enc) # Parse arguments that canto shares with canto-fetch, return # a lot of file locations and an optlist that will contain the # parsed, but yet unused canto specific arguments. conf_dir, log_file, conf_file, feed_dir, script_dir, optlist =\ args.parse_common_args(enc, "hvulaor:t:i:n:", ["help","version","update","list","checkall","opml", "import=","url=","checknew=","tag="]) # Instantiate the config and start the log. try: self.cfg = get_cfg(conf_file, log_file, feed_dir, script_dir) self.cfg.parse() except: traceback.print_exc() upgrade_help() sys.exit(-1) self.cfg.log("Canto v %s (%s)" % \ ("%d.%d.%d" % VERSION_TUPLE, GIT_SHA), "w") self.cfg.log("Time: %s" % time.asctime()) self.cfg.log("Config parsed successfully.") # If we were passed an existing curses screen (i.e. restart) # pass it through to the config. self.cfg.stdscr = stdscr if self.cfg.stdscr: self.restarting = True else: self.restarting = False self.restart = False # Default arguments. flags = 0 feed_ct = None opml_file = None url = None newtag = None # Note that every single flag that takes an argument has its # argument converted to unicode. Saves a lot of bullshit later. for opt, arg in optlist: if opt in ["-u", "--update"]: flags |= UPDATE_FIRST elif opt in ["-n", "--checknew"]: flags |= CHECK_NEW feed_ct = unicode(arg, enc, "ignore") elif opt in ["-a", "--checkall"]: flags |= CHECK_NEW elif opt in ["-l", "--list"]: flags |= FEED_LIST elif opt in ["-o", "--opml"]: flags |= OUT_OPML elif opt in ["-i", "--import"]: flags |= IN_OPML opml_file = unicode(arg, enc, "ignore") elif opt in ["-r", "--url"]: flags |= IN_URL url = unicode(arg, enc, "ignore") elif opt in ["-t", "--tag"]: newtag = unicode(arg, enc, "ignore") # Import flags harness the same functions as their config # based counterparts, source_opml and source_url. if flags & IN_OPML: self.cfg.locals['source_opml'](opml_file, append=True) print "OPML imported." if flags & IN_URL: self.cfg.locals['source_url'](url, append=True, tag=newtag) print "URL added." # All import options should terminate. if flags & (IN_OPML + IN_URL): sys.exit(0) # If self.cfg had to generate a config, make sure we # update first. if self.cfg.no_conf: self.cfg.log("Conf was auto-generated, adding -u") flags |= UPDATE_FIRST if flags & UPDATE_FIRST: self.cfg.log("Pausing to update...") canto_fetch.run(self.cfg, True, True) # Detect if there are any new feeds by whether their # set path exists. If not, run canto-fetch but don't # force it, so canto-fetch intelligently updates. for i, f in enumerate(self.cfg.feeds): if not os.path.exists(f.path): self.cfg.log("Detected unfetched feed: %s." % f.URL) canto_fetch.run(self.cfg, True, False) #Still no go? if not os.path.exists(f.path): self.cfg.log("Failed to fetch %s, removing" % f.URL) self.cfg.feeds[i] = None else: self.cfg.log("Fetched.\n") break # Collapse the feed array, if we had to remove some unfetchables. self.cfg.feeds = filter(lambda x: x != None, self.cfg.feeds) self.new = [] self.old = [] self.ph = ProcessHandler(self.cfg) # Force an update from disk by queueing a work item for each thread. # At this point, we just want to do the portion of the update where the # disk is read, so PROC_UPDATE is used. self.cfg.log("Populating feeds...") for f in self.cfg.feeds: self.ph.send((PROC_UPDATE, f.URL, [])) for f in self.cfg.feeds: f.merge(self.ph.recv()[1]) self.ph.send((PROC_GETTAGS, )) fixedtags = self.ph.recv() self.ph.kill_process() for i, f in enumerate(self.cfg.feeds): self.cfg.feeds[i].tags = fixedtags[i] # Now that the tags have all been straightened out, validate the config. # Making sure the tags are unique before validation is important because # part of validation is the actual creation of Tag() objects. try: self.cfg.validate() except Exception, err: print err upgrade_help() sys.exit(0) # Print out a feed list if flags & FEED_LIST: for f in self.cfg.feeds: print f.tags[0].encode(enc, "ignore") sys.exit(0) # This could probably be done better, or more officially # with some XML library, but for now, print is working # fairly well. if flags & OUT_OPML: self.cfg.log("Outputting OPML") print """<opml version="1.0">""" print """<body>""" for feed in self.cfg.feeds: ufp = feed.get_ufp() if "atom" in ufp["version"]: t = "pie" else: t = "rss" print """\t<outline text="%s" xmlUrl="%s" type="%s" />""" %\ (feed.tags[0].encode(enc, "ignore"), feed.URL, t) print """</body>""" print """</opml>""" sys.exit(0) # Handle -a/-n flags (print number of new items) if flags & CHECK_NEW: if not feed_ct: unread = 0 for f in self.cfg.feeds: for s in f: if "read" not in s["canto_state"]: unread += 1 print unread else: t = [f for f in self.cfg.feeds if f.tags[0] == feed_ct] if not t: print "Unknown Feed" else: print len([ x for x in t[0] if "read"\ not in x["canto_state"]]) sys.exit(0) # After this point, we know that all further operation is going to # require all tags to be populated, we queue up the latter # half of the work, to actually filter and sort the items. # The reason we clear the feeds first is that only after validation do # we know what keys are going to have to be precached, and canto tries # hard to conserve items (see feed.merge), so we need to replace all of # them with corrected, fresh items from the process that knows about the # precache for f in self.cfg.feeds: del f[:] signal.signal(signal.SIGCHLD, self.chld) self.update(1, self.cfg.feeds, PROC_BOTH) # At this point we know that we're going to actually launch # the client, so we fire up ncurses and add the screen # information to our Cfg(). if not self.restarting: self.cfg.stdscr = curses.initscr() self.cfg.stdscr.nodelay(1) # curs_set can return ERR, we shouldn't care try: curses.curs_set(0) except: pass # if any of these mess up though, the rest of the # the operation is suspect, so die. try: curses.noecho() curses.start_color() curses.use_default_colors() except: self.cfg.log("Unable to init curses, bailing") self.done() self.sigusr = 0 self.resize = 0 self.alarmed = 0 self.ticks = 60 self.cfg.height, self.cfg.width = self.cfg.stdscr.getmaxyx() # Init colors try: for i, (fg, bg) in enumerate(self.cfg.colors): curses.init_pair(i + 1, fg, bg) except: self.cfg.log("Unable to init curses color pairs!") self.cfg.log("Curses initialized.") # Instantiate the base Gui class self.gui = Gui(self.cfg, self.cfg.tags.cur()) self.cfg.log("GUI initialized.") # Signal handling signal.signal(signal.SIGWINCH, self.winch) signal.signal(signal.SIGALRM, self.alarm) signal.signal(signal.SIGINT, self.done) signal.signal(signal.SIGUSR1, self.sigusr) signal.alarm(1) self.cfg.log("Signals set.") self.estring = None # The main loop is wrapped in one huge try...except so that no matter # what exception is thrown, we can clean up curses and exit without # s******g all over the terminal. try: # Initial draw of the screen, if not restarting self.refresh(self.restarting) self.restarting = False # Main program loop, terminated when all handlers have # deregistered / exited. self.cfg.log("Beginning main loop.") while 1: # Clear the key t = None # Gui is none if a key returned EXIT if not self.gui: break # If we've spawned a text-based process (i.e. elinks), then just # pause and wait to be awakened by SIGCHLD if self.cfg.wait_for_pid: signal.pause() # Tick when SIGALRM is received. if self.alarmed: self.alarmed = 0 self.tick() # Deferred update from signal if self.sigusr: self.sigusr = 0 if "signal" in self.cfg.triggers: self.update() # Get the key k = self.cfg.stdscr.getch() # KEY_RESIZE is the only key not propagated, to # keep users from rebinding it and crashing. if k == curses.KEY_RESIZE or self.resize: self.resize = 0 self.refresh() continue # No input, time to check on the worker. if k == -1: # Make sure we don't pin the CPU, so if there's no input and # no waiting updates, sleep for awhile. if not self.ph.pid: time.sleep(0.01) continue r = self.ph.recv(True, 0.01) if r: feed = [f for f in self.cfg.feeds if f.URL == r[1]][0] if r[0] == PROC_UPDATE: old = [] for gf, tf, s, l in r[4]: if not l: old.append((gf, tf, s, l)) continue for i, oldidx in enumerate(l): l[i] = feed[oldidx] old.append((gf, tf, s, l)) feed.merge(r[2]) new = [] for gf, tf, s, l in r[3]: if not l: new.append((gf, tf, s, None)) continue for i, newidx in enumerate(l): l[i] = (feed[newidx], newidx) new.append((gf, tf, s, l)) self.gui.alarm(new, old) self.gui.draw_elements() feed.qd = False continue # Handle Meta pairs elif k == 195: k2 = self.cfg.stdscr.getch() if k2 >= 64: t = (k2 - 64, 1) else: t = (k, 0) # Just a normal key-press else: t = (k, 0) # Key resolves a keypress tuple into a list of actions actions = self.gui.key(t) # Actions are executed in order, and each return code # is handled in order. for a in actions: r = self.gui.action(a) if r == REFRESH_ALL: self.refresh() elif r == UPDATE: self.update() elif r in [REFILTER, RETAG]: self.ph.flush() self.update(1) elif r == TFILTER: # Tag filters shouldn't perform a full update, so we map # the relevant tag to all of the feeds that include that # tag and update them. t = self.gui.sel["tag"] ufds = [ f for f in self.cfg.feeds\ if t.tag in f.tags] t.clear() self.update(1, ufds) elif r == REDRAW_ALL: self.gui.draw_elements() elif r == RESTART: self.restart = True self.gui = None elif r == EXIT: self.gui = None break except Exception: self.estring = traceback.format_exc() except KeyboardInterrupt: pass self.done()