示例#1
0
    def prot_itemsdone(self, empty):
        if self.item_tag == None:
            return

        if self.discard:
            self.item_tag = None
            self.item_buf = []
            self.item_removes = []
            self.item_adds = []
            return

        if self.item_adds:
            self.item_tag.add_items(self.item_adds)

        # Eliminate discarded items. This has to be done here, so we have
        # access to all of the items given in the multiple ITEM responses.

        for id in self.item_tag:
            if id not in self.item_buf:
                self.item_removes.append(id)

        if self.item_removes:
            self.item_tag.remove_items(self.item_removes)

        self.item_tag = None
        self.item_buf = []
        self.item_removes = []
        self.item_adds = []

        if self.still_updating:
            self.still_updating -= 1
            if not self.still_updating:
                log.debug("Calling curses_update_complete")
                call_hook("curses_update_complete", [])
示例#2
0
    def run(self):
        # We want this as early as possible
        signal.signal(signal.SIGUSR1, self.sigusr1)

        # Get config from daemon
        if not config.init(self, CANTO_PROTOCOL_COMPATIBLE):
            print("Invalid daemon version")
            print("Wanted: %s" % CANTO_PROTOCOL_COMPATIBLE)
            print("Got: %s" % config.version)
            sys.exit(-1)
        else:
            log.info("Version check passed: %s" % CANTO_PROTOCOL_COMPATIBLE)

        # Create Tags for each TagCore
        self.gui = CantoCursesGui(self, self.glog_handler)
        tag_updater.init(self)

        # Initial signal setup.
        signal.signal(signal.SIGWINCH, self.winch)
        signal.signal(signal.SIGCHLD, self.child)

        finalize_eval_settings()

        call_hook("curses_start", [])

        if self.plugin_errors:
            log.error("The following error occurred loading plugins:\n\n%s" %
                      self.plugin_errors)

        while self.gui.alive:
            self.gui.tick()
            time.sleep(1)
示例#3
0
    def prot_deltags(self, tags):
        if not self.initd:
            for tag in tags:
                if tag in self.vars["strtags"]:
                    self.vars["strtags"].remove(tag)
                if tag in self.config["tagorder"]:
                    self.config["tagorder"].append(tag)
            return

        c = self.get_conf()
        changes = False

        for tag in tags:
            if tag in self.vars["strtags"]:
                if tag in c["tagorder"]:
                    c["tagorder"] = [ x for x in self.config["tagorder"] if x != tag ]
                    changes = True
                self.vars["strtags"].remove(tag)
                call_hook("curses_del_tag", [ tag ])
                self.eval_tags()
            else:
                log.debug("Got DELTAG for non-existent tag!")

        if changes:
            self.set_conf(c)
示例#4
0
    def eval_tags(self):
        prevtags = self.vars["curtags"]

        sorted_tags = []
        r = re.compile(self.config["tags"])

        for tag in self.vars["strtags"]:

            # This can happen between the time that a tag is removed from the config
            # and the time that we receive a DELTAG event.
            if tag not in self.config["tagorder"]:
                continue

            elif r.match(tag):
                sorted_tags.append((self.config["tagorder"].index(tag), tag))
        sorted_tags.sort()

        self.set_var("curtags", [ x for (i, x) in sorted_tags ])

        if not self.vars["curtags"]:
            log.warn("NOTE: Current 'tags' setting eliminated all tags!")

        # If evaluated tags differ, we need to let other know.

        if prevtags != self.vars["curtags"]:
            log.debug("Evaluated Tags Changed:\n%s\n", json.dumps(self.vars["curtags"], indent=4))
            call_hook("curses_eval_tags_changed", [])
示例#5
0
    def eval_tags(self):
        prevtags = self.vars["curtags"]

        sorted_tags = []
        r = re.compile(self.config["tags"])

        for tag in self.vars["strtags"]:

            # This can happen between the time that a tag is removed from the config
            # and the time that we receive a DELTAG event.
            if tag not in self.config["tagorder"]:
                continue

            elif r.match(tag):
                sorted_tags.append((self.config["tagorder"].index(tag), tag))
        sorted_tags.sort()

        self.set_var("curtags", [x for (i, x) in sorted_tags])

        if not self.vars["curtags"]:
            log.warn("NOTE: Current 'tags' setting eliminated all tags!")

        # If evaluated tags differ, we need to let other know.

        if prevtags != self.vars["curtags"]:
            log.debug("Evaluated Tags Changed:\n%s\n",
                      json.dumps(self.vars["curtags"], indent=4))
            call_hook("curses_eval_tags_changed", [])
示例#6
0
    def prot_deltags(self, tags):
        if not self.initd:
            for tag in tags:
                if tag in self.vars["strtags"]:
                    self.vars["strtags"].remove(tag)
                if tag in self.config["tagorder"]:
                    self.config["tagorder"].append(tag)
            return

        c = self.get_conf()
        changes = False

        for tag in tags:
            if tag in self.vars["strtags"]:
                if tag in c["tagorder"]:
                    c["tagorder"] = [
                        x for x in self.config["tagorder"] if x != tag
                    ]
                    changes = True
                self.vars["strtags"].remove(tag)
                call_hook("curses_del_tag", [tag])
                self.eval_tags()
            else:
                log.debug("Got DELTAG for non-existent tag!")

        if changes:
            self.set_conf(c)
示例#7
0
def on_daemon_serving():
    log.debug("Synchronizing subscriptions.")
    ino_subs = api.get_subs()

    for c_feed in config.json["feeds"]:
        url = c_feed["url"]

        for sub in ino_subs:
            if sub["url"] == url:
                break
        else:
            log.debug("Old feed: %s", url)
            call_hook("daemon_del_configs", [None, {"feeds": [c_feed]}])

    for sub in ino_subs:
        url = sub["url"]
        name = sub["title"]

        for c_feed in config.json["feeds"]:
            if c_feed["url"] == url:
                break
            if c_feed["name"] == name:
                log.info("Found feed with same name, but not URL? Skipping.")
                break
        else:
            log.debug("New feed: %s", url)
            call_hook("daemon_set_configs",
                      [None, {
                          "feeds": [{
                              "name": name,
                              "url": url
                          }]
                      }])
示例#8
0
    def prot_deltags(self, tags):

        if not self.initd:
            for tag in tags:
                if tag in self.vars["strtags"]:
                    self.vars["strtags"].remove(tag)
                if tag in self.config["tagorder"]:
                    self.config["tagorder"].append(tag)
            return

        c = self.get_conf()

        for tag in tags:
            if tag in self.vars["strtags"]:
                new_alltags = self.vars["alltags"]

                i, tagobj = [ x for x in enumerate(new_alltags) if x[1].tag == tag ][0]
                tagobj.die()

                # Remove it from our vars.
                del new_alltags[i]
                self.vars["alltags"] = new_alltags
                self.vars["strtags"].remove(tag)

                call_hook("curses_del_tag", tag)
            else:
                log.debug("Got DELTAG for non-existent tag!")

            if tag in c["tagorder"]:
                c["tagorder"] = [ x for x in self.config["tagorder"] if x != tag ]

        self.set_conf(c)
        self.eval_tags()
示例#9
0
    def run(self):
        # We want this as early as possible
        signal.signal(signal.SIGUSR1, self.sigusr1)

        # Get config from daemon
        if not config.init(self, CANTO_PROTOCOL_COMPATIBLE):
            print("Invalid daemon version")
            print("Wanted: %s" % CANTO_PROTOCOL_COMPATIBLE)
            print("Got: %s" % config.version)
            sys.exit(-1)
        else:
            log.info("Version check passed: %s" % CANTO_PROTOCOL_COMPATIBLE)

        # Create Tags for each TagCore
        self.gui = CantoCursesGui(self, self.glog_handler)
        tag_updater.init(self)

        # Initial signal setup.
        signal.signal(signal.SIGWINCH, self.winch)
        signal.signal(signal.SIGCHLD, self.child)

        finalize_eval_settings()

        call_hook("curses_start", [])

        if self.plugin_errors:
            log.error("The following error occurred loading plugins:\n\n%s" % self.plugin_errors)

        while self.gui.alive:
            self.gui.tick()
            time.sleep(1)
示例#10
0
def sync_subscriptions():
    log.info("Syncing subscriptions with Google")

    auth = ClientAuthMethod(USERNAME, PASSWORD)
    reader = GoogleReader(auth)
    reader.buildSubscriptionList()

    gurls = [ (f.title, f.feedUrl) for f in reader.getSubscriptionList() ]
    curls = [ f.URL for f in allfeeds.get_feeds() ]
    names = [ f.name for f in allfeeds.get_feeds() ]

    new_feeds = []
    for gtitle, gurl in gurls[:]:
        if gurl not in curls:

            # Handle name collisions because we're not prepared to handle ERROR
            # responses from config

            if gtitle in names:
                offset = 2
                while (gtitle + " (%d)" % offset) in names:
                    offset += 1
                gtitle = gtitle + " (%d)" % offset

            attrs = { "url" : gurl, "name" : gtitle }
            new_feeds.append(attrs)
            names.append(gtitle)

    call_hook("set_configs", [ None, { "feeds" : new_feeds }])

    for curl in curls[:]:
        if curl not in gurls:
            reader.subscribe('feed/' + curl)
示例#11
0
def on_daemon_serving():
    log.debug("Synchronizing subscriptions.")
    ino_subs = api.get_subs()

    for c_feed in config.json["feeds"]:
        url = c_feed["url"]

        for sub in ino_subs:
            if sub["url"] == url:
                break
        else:
            log.debug("Old feed: %s", url)
            call_hook("daemon_del_configs", [ None, { "feeds" : [ c_feed ] } ] )

    for sub in ino_subs:
        url = sub["url"]
        name = sub["title"]

        for c_feed in config.json["feeds"]:
            if c_feed["url"] == url:
                break
            if c_feed["name"] == name:
                log.info("Found feed with same name, but not URL? Skipping.")
                break
        else:
            log.debug("New feed: %s", url)
            call_hook("daemon_set_configs", [ None, { "feeds" : [ { "name" : name, "url" : url } ] } ])
示例#12
0
    def reset(self):
        self.lock.acquire_write()

        call_hook("curses_items_removed", [ self, self[:] ])
        del self[:]

        self.changed()
        self.lock.release_write()
示例#13
0
    def set_var(self, tweak, value):
        # We only care if the value is different, or it's a message
        # value, which should always cause a fresh message display,
        # even if it's the same error as before.

        if self.vars[tweak] != value:
            self.vars[tweak] = value
            call_hook("curses_var_change", [{tweak: value}])
示例#14
0
    def set_var(self, tweak, value):
        # We only care if the value is different, or it's a message
        # value, which should always cause a fresh message display,
        # even if it's the same error as before.

        if self.vars[tweak] != value:
            self.vars[tweak] = value
            call_hook("curses_var_change", [{ tweak : value }])
示例#15
0
    def reset(self):
        for item in self:
            item.die()

        call_hook("curses_items_removed", [ self, self[:] ])
        del self[:]

        # Request redraw to update item counts.
        self.need_redraw()
示例#16
0
 def on_del_tag(self, tag):
     for tagcore in alltagcores[:]:
         if tagcore.tag == tag:
             if len(tagcore):
                 call_hook("curses_items_removed", [ tagcore, tagcore ] )
                 tagcore.set_items([])
             call_hook("curses_del_tagcore", [ tagcore ])
             alltagcores.remove(tagcore)
             while tagcore in self.updating:
                 self.updating.remove(tagcore)
             return
示例#17
0
 def on_del_tag(self, tag):
     for tagcore in alltagcores[:]:
         if tagcore.tag == tag:
             if len(tagcore):
                 call_hook("curses_items_removed", [tagcore, tagcore])
                 tagcore.set_items([])
             call_hook("curses_del_tagcore", [tagcore])
             alltagcores.remove(tagcore)
             while tagcore in self.updating:
                 self.updating.remove(tagcore)
             return
示例#18
0
    def add_items(self, ids):
        self.lock.acquire_write()

        added = []
        for id in ids:
            self.append(id)
            added.append(id)

        call_hook("curses_items_added", [ self, added ] )

        self.changed()
        self.lock.release_write()
示例#19
0
    def prot_configs(self, given, write = False):
        log.debug("prot_configs given:\n%s\n" % json.dumps(given, indent=4, sort_keys=True))

        if "tags" in given:
            for tag in list(given["tags"].keys()):
                ntc = given["tags"][tag]

                tc = self.get_tag_conf(tag)

                changes, deletions =\
                        self.validate_config(ntc, tc, self.tag_validators)

                if changes:
                    self.tag_config[tag] = ntc
                    call_hook("curses_tag_opt_change", [ { tag : changes } ])

                    if write:
                        self.write("SETCONFIGS", { "tags" : { tag : changes }})

                if deletions and write:
                    self.write("DELCONFIGS", { "tags" : { tag : deletions }})

        if "CantoCurses" in given:
            new_config = given["CantoCurses"]

            changes, deletions =\
                    self.validate_config(new_config, self.config,\
                    self.validators)

            if changes:
                self.config = new_config
                call_hook("curses_opt_change", [ changes ])

                if write:
                    self.write("SETCONFIGS", { "CantoCurses" : changes })

            if deletions and write:
                self.write("DELCONFIGS", { "CantoCurses" : deletions })

        if "defaults" in given:

            # We don't honor any default settings, so just record them
            # and pass them on to the daemon if write

            self.daemon_defaults.update(given["defaults"])
            if write:
                self.write("SETCONFIGS", { "defaults" : self.daemon_defaults })

            call_hook("curses_def_opt_change", [ given["defaults"] ])

        if "feeds" in given:

            self.daemon_feedconf = given["feeds"]
            if write:
                self.write("SETCONFIGS", { "feeds" : self.daemon_feedconf })

            call_hook("curses_feed_opt_change", [ given["feeds"] ])

        self.initd = True
示例#20
0
    def start(self):
        try:
            self.init()
            self.run()
        except KeyboardInterrupt:
            pass

        except Exception as e:
            tb = traceback.format_exc()
            log.error("Exiting on exception:")
            log.error("\n" + "".join(tb))

        call_hook("curses_exit", [])
        log.info("Exiting.")
        sys.exit(0)
示例#21
0
    def start(self):
        try:
            self.init()
            self.run()
        except KeyboardInterrupt:
            pass

        except Exception as e:
            tb = traceback.format_exc()
            log.error("Exiting on exception:")
            log.error("\n" + "".join(tb))

        call_hook("curses_exit", [])
        log.info("Exiting.")
        sys.exit(0)
示例#22
0
    def add_items(self, ids):
        added = []
        for id in ids:
            s = Story(id, self.callbacks)
            self.append(s)
            added.append(s)

            rel = len(self) - 1
            s.set_rel_offset(rel)
            s.set_offset(self.item_offset + rel)
            s.set_sel_offset(self.sel_offset + rel)

        # Request redraw to update item counts.
        self.need_redraw()

        call_hook("curses_items_added", [ self, added ] )
示例#23
0
    def reset(self):

        # Tag should be sorted on sync if we were reset, regardless of whether
        # a sync was done when the tag was empty, so keep track of this and
        # the Tag object will clear it on sync.

        self.was_reset = True

        self.lock.acquire_write()

        if len(self):
            call_hook("curses_items_removed", [ self, self[:] ])
        del self[:]

        self.changed()
        self.lock.release_write()
示例#24
0
    def set_var(self, tweak, value):
        # We only care if the value is different, or it's a message
        # value, which should always cause a fresh message display,
        # even if it's the same error as before.

        config_lock.acquire_write()
        if self.vars[tweak] != value:

            # If we're selecting or unselecting a story, then
            # we need to make sure it doesn't disappear.

            self.vars[tweak] = value
            config_lock.release_write()

            call_hook("curses_var_change", [{ tweak : value }])
        else:
            config_lock.release_write()
示例#25
0
    def prot_newtags(self, tags):

        if not self.initd:
            for tag in tags:
                if tag not in self.vars["strtags"]:
                    self.vars["strtags"].append(tag)
                if tag not in self.config["tagorder"]:
                    self.config["tagorder"].append(tag)
            return

        c = self.get_conf()

        # Likely the same as tags
        changes = False
        newtags = []

        for tag in tags:
            if tag not in c["tagorder"]:
                c["tagorder"] = c["tagorder"] + [tag]
                changes = True

            if tag not in self.vars["strtags"]:

                # If we don't have configuration for this
                # tag already, substitute the default template.

                if tag not in self.tag_config:
                    log.debug("Using default tag config for %s", tag)
                    self.tag_config[tag] = self.tag_template_config.copy()

                self.vars["strtags"].append(tag)
                newtags.append(tag)
                changes = True

        # If there aren't really any tags we didn't know about, no bail.

        if not changes:
            return

        self.set_conf(c)

        for tag in newtags:
            log.debug("New tag %s", tag)
            call_hook("curses_new_tag", [tag])

        self.eval_tags()
示例#26
0
    def prot_newtags(self, tags):

        if not self.initd:
            for tag in tags:
                if tag not in self.vars["strtags"]:
                    self.vars["strtags"].append(tag)
                if tag not in self.config["tagorder"]:
                    self.config["tagorder"].append(tag)
            return

        c = self.get_conf()

        # Likely the same as tags
        changes = False
        newtags = []

        for tag in tags:
            if tag not in c["tagorder"]:
                c["tagorder"] = c["tagorder"] + [ tag ]
                changes = True

            if tag not in self.vars["strtags"]:

                # If we don't have configuration for this
                # tag already, substitute the default template.

                if tag not in self.tag_config:
                    log.debug("Using default tag config for %s", tag)
                    self.tag_config[tag] = self.tag_template_config.copy()

                self.vars["strtags"].append(tag)
                newtags.append(tag)
                changes = True

        # If there aren't really any tags we didn't know about, no bail.

        if not changes:
            return

        self.set_conf(c)

        for tag in newtags:
            log.debug("New tag %s", tag)
            call_hook("curses_new_tag", [ tag ])

        self.eval_tags()
示例#27
0
    def prot_attributes(self, d):
        # Update attributes, and then notify everyone to grab new content.
        self.lock.acquire_write()

        for key in d.keys():
            if key in self.attributes:

                # If we're updating, we want to create a whole new dict object
                # so that our stories dicts don't get updated without a sync

                cp = self.attributes[key].copy()
                cp.update(d[key])
                self.attributes[key] = cp
            else:
                self.attributes[key] = d[key]
        self.lock.release_write()

        call_hook("curses_attributes", [self.attributes])
示例#28
0
    def prot_attributes(self, d):
        # Update attributes, and then notify everyone to grab new content.
        self.lock.acquire_write()

        for key in d.keys():
            if key in self.attributes:

                # If we're updating, we want to create a whole new dict object
                # so that our stories dicts don't get updated without a sync

                cp = self.attributes[key].copy()
                cp.update(d[key])
                self.attributes[key] = cp
            else:
                self.attributes[key] = d[key]
        self.lock.release_write()

        call_hook("curses_attributes", [ self.attributes ])
示例#29
0
    def remove_items(self, ids):
        self.lock.acquire_write()

        removed = []

        # Copy self so we can remove from self
        # without screwing up iteration.

        for idx, id in enumerate(self[:]):
            if id in ids:
                log.debug("removing: %s" % (id,))

                list.remove(self, id)
                removed.append(id)

        call_hook("curses_items_removed", [ self, removed ] )

        self.changed()
        self.lock.release_write()
示例#30
0
    def eval_tags(self):
        prevtags = self.vars["curtags"]

        sorted_tags = []
        r = re.compile(self.config["tags"])
        for tag in self.vars["alltags"]:
            if r.match(tag.tag):
                sorted_tags.append((self.config["tagorder"].index(tag.tag), tag))
        sorted_tags.sort()

        self.set_var("curtags", [ x for (i, x) in sorted_tags ])

        if not self.vars["curtags"]:
            log.warn("NOTE: Current 'tags' setting eliminated all tags!")

        # If evaluated tags differ, we need to refresh.

        if prevtags != self.vars["curtags"] and self.screen:
            log.debug("Evaluated Tags Changed: %s" % [ t.tag for t in self.vars["curtags"]])
            call_hook("curses_eval_tags_changed", [])
示例#31
0
    def prot_items(self, updates):
        # Daemon should now only return with one tag in an items response

        tag = list(updates.keys())[0]

        for have_tag in alltagcores:
            if have_tag.tag == tag:
                break
        else:
            return

        sorted_updated_ids = list(enumerate(updates[tag]))
        sorted_updated_ids.sort(key=lambda x: x[1])

        sorted_current_ids = list(enumerate(have_tag))
        sorted_updated_ids.sort(key=lambda x: x[1])

        new_ids = []
        cur_ids = []
        old_ids = []

        for c_place, c_id in sorted_current_ids:
            while sorted_updated_ids and c_id > sorted_updated_ids[0][1]:
                new_ids.append(sorted_updated_ids.pop(0))

            if not sorted_updated_ids or c_id < sorted_updated_ids[0][1]:
                old_ids.append(c_id)
            else:
                place = sorted_updated_ids.pop(0)[0]
                cur_ids.append((place, c_id))

        new_ids += sorted_updated_ids

        all_ids = new_ids + cur_ids
        all_ids.sort()

        have_tag.set_items([x[1] for x in all_ids])

        if new_ids:
            call_hook("curses_items_added",
                      [have_tag, [x[1] for x in new_ids]])

        if old_ids:
            call_hook("curses_items_removed", [have_tag, old_ids])

        if have_tag in self.updating:
            have_tag.was_reset = True
            call_hook("curses_tag_updated", [have_tag])
            self.updating.remove(have_tag)
            if self.updating == []:
                call_hook("curses_update_complete", [])
示例#32
0
    def prot_items(self, updates):
        # Daemon should now only return with one tag in an items response

        tag = list(updates.keys())[0]

        for have_tag in alltagcores:
            if have_tag.tag == tag:
                break
        else:
            return

        sorted_updated_ids = list(enumerate(updates[tag]))
        sorted_updated_ids.sort(key=lambda x : x[1])

        sorted_current_ids = list(enumerate(have_tag))
        sorted_updated_ids.sort(key=lambda x : x[1])

        new_ids = []
        cur_ids = []
        old_ids = []

        for c_place, c_id in sorted_current_ids:
            while sorted_updated_ids and c_id > sorted_updated_ids[0][1]:
                new_ids.append(sorted_updated_ids.pop(0))

            if not sorted_updated_ids or c_id < sorted_updated_ids[0][1]:
                old_ids.append(c_id)
            else:
                place = sorted_updated_ids.pop(0)[0]
                cur_ids.append((place, c_id))

        new_ids += sorted_updated_ids

        all_ids = new_ids + cur_ids
        all_ids.sort()

        have_tag.set_items([x[1] for x in all_ids])

        if new_ids:
            call_hook("curses_items_added", [ have_tag, [x[1] for x in new_ids] ] )

        if old_ids:
            call_hook("curses_items_removed", [ have_tag, old_ids ] )

        if have_tag in self.updating:
            have_tag.was_reset = True
            call_hook("curses_tag_updated", [ have_tag ])
            self.updating.remove(have_tag)
            if self.updating == []:
                call_hook("curses_update_complete", [])
示例#33
0
    def cmd_syncto(self, socket=None, args=None):
        if self.fresh_content:
            f, fname = mkstemp()
            os.close(f)

            # Lock feeds to make sure nothing's in flight
            wlock_all()

            # Sync the shelf so it's all on disk

            self.backend.shelf.sync()

            shutil.copyfile(self.backend.feed_path, fname)

            # Let everything else continue
            wunlock_all()

            call_hook("daemon_syncto", ["db", fname])

            # Cleanup temp file
            os.unlink(fname)

            self.fresh_content = False
            self.sent_content = True

        if self.fresh_config:
            f, fname = mkstemp()
            os.close(f)

            config_lock.acquire_read()
            shutil.copyfile(self.backend.conf_path, fname)
            config_lock.release_read()

            call_hook("daemon_syncto", ["conf", fname])

            os.unlink(fname)

            self.fresh_config = False
            self.sent_config = True
示例#34
0
    def cmd_syncto(self, socket = None, args = None):
        if self.fresh_content:
            f, fname = mkstemp()
            os.close(f)

            # Lock feeds to make sure nothing's in flight
            wlock_all()

            # Sync the shelf so it's all on disk

            self.backend.shelf.sync()

            shutil.copyfile(self.backend.feed_path, fname)

            # Let everything else continue
            wunlock_all()

            call_hook("daemon_syncto", [ "db", fname ])

            # Cleanup temp file
            os.unlink(fname)

            self.fresh_content = False
            self.sent_content = True

        if self.fresh_config:
            f, fname = mkstemp()
            os.close(f)

            config_lock.acquire_read()
            shutil.copyfile(self.backend.conf_path, fname)
            config_lock.release_read()

            call_hook("daemon_syncto", [ "conf", fname ])

            os.unlink(fname)

            self.fresh_config = False
            self.sent_config = True
示例#35
0
    def set_var(self, tweak, value):
        # We only care if the value is different, or it's a message
        # value, which should always cause a fresh message display,
        # even if it's the same error as before.

        if self.vars[tweak] != value:

            # If we're selecting or unselecting a story, then
            # we need to make sure it doesn't disappear.

            if tweak in [ "selected", "reader_item" ]:
                if self.vars[tweak] and hasattr(self.vars[tweak], "id"):
                    self.vars["protected_ids"].remove(self.vars[tweak].id)
                    self.write("UNPROTECT",\
                            { "filter-immune" : [ self.vars[tweak].id ] })

                    # Fake a TAGCHANGE because unprotected items have the
                    # possibility to filtered out and we only refresh items for
                    # tags that get a TAGCHANGE on tick.

                    for tag in self.vars["alltags"]:
                        if self.vars[tweak].id in tag.get_ids():
                            self.prot_tagchange(tag.tag)

                if value and hasattr(value, "id"):
                    # protected_ids just tells the prot_items to not allow
                    # this item to have it's auto protection stripped.

                    self.vars["protected_ids"].append(value.id)

                    # Set an additional protection, filter-immune so hardened
                    # filters won't eliminate it.

                    self.write("PROTECT", { "filter-immune" : [ value.id ] })

            self.vars[tweak] = value

            call_hook("curses_var_change", [{ tweak : value }])
示例#36
0
    def prot_configs(self, given, write = False):
        log.debug("prot_configs given: %s" % given)

        if "tags" in given:
            for tag in list(given["tags"].keys()):
                ntc = given["tags"][tag]
                tc = self.tag_config[tag]

                changes, deletions =\
                        self.validate_config(ntc, tc, self.tag_validators)

                if changes:
                    self.tag_config[tag] = ntc
                    call_hook("curses_tag_opt_change", [ { tag : changes } ])

                    if write:
                        self.write("SETCONFIGS", { "tags" : { tag : changes }})

                if deletions and write:
                    self.write("DELCONFIGS", { "tags" : { tag : deletions }})

        if "CantoCurses" in given:
            new_config = given["CantoCurses"]

            changes, deletions =\
                    self.validate_config(new_config, self.config,\
                    self.validators)

            if changes:
                self.config = new_config
                call_hook("curses_opt_change", [ changes ])

                if write:
                    self.write("SETCONFIGS", { "CantoCurses" : changes })

            if deletions and write:
                self.write("DELCONFIGS", { "CantoCurses" : deletions })
示例#37
0
    def remove_items(self, ids):
        removed = []

        # Copy self so we can remove from self
        # without screwing up iteration.

        for idx, item in enumerate(self[:]):
            if item.id in ids:
                log.debug("removing: %s" % (item.id,))

                list.remove(self, item)
                item.die()
                removed.append(item)

        # Update indices of items.
        for i, story in enumerate(self):
            story.set_rel_offset(i)
            story.set_offset(self.item_offset + i)
            story.set_sel_offset(self.sel_offset + i)

        # Request redraw to update item counts.
        self.need_redraw()

        call_hook("curses_items_removed", [ self, removed ] )
示例#38
0
 def on_new_tag(self, tag):
     self.write("WATCHTAGS", [tag])
     self.prot_tagchange(tag)
     call_hook("curses_new_tagcore", [TagCore(tag)])
    def check(self):
        config_script = {
            'VERSION' : { '*' : [('VERSION', CANTO_PROTOCOL_COMPATIBLE)] },
            'CONFIGS' : { '*' : [('CONFIGS', { "CantoCurses" : config.template_config })] },
                
        }

        config_backend = TestBackend("config", config_script)

        config.init(config_backend, CANTO_PROTOCOL_COMPATIBLE)

        config_backend.inject("NEWTAGS", [ "maintag:Slashdot", "maintag:reddit" ])

        tagcore_script = {}

        tag_backend = TestBackend("tagcore", tagcore_script)

        on_hook("curses_items_removed", self.on_items_removed)
        on_hook("curses_items_added", self.on_items_added)
        on_hook("curses_new_tagcore", self.on_new_tagcore)
        on_hook("curses_del_tagcore", self.on_del_tagcore)
        on_hook("curses_attributes", self.on_attributes)
        on_hook("curses_update_complete", self.on_update_complete)
        on_hook("curses_tag_updated", self.on_tag_updated)

        # 1. Previously existing tags in config should be populated on init

        self.reset_flags()

        tag_updater.init(tag_backend)

        for tag in config.vars["strtags"]:
            for tc in alltagcores:
                if tc.tag == tag:
                    break
            else:
                raise Exception("Couldn't find TC for tag %s" % tag)

        self.compare_flags(NEW_TC)

        self.reset_flags()

        # 2. Getting empty ITEMS responses should cause no events

        tag_backend.inject("ITEMS", { "maintag:Slashdot" : [] })
        tag_backend.inject("ITEMSDONE", {})

        tag_backend.inject("ITEMS", { "maintag:reddit" : [] })
        tag_backend.inject("ITEMSDONE", {})

        self.compare_flags(0)

        # 3. Getting a non-empty ITEMS response should cause items_added

        tag_backend.inject("ITEMS", { "maintag:Slashdot" : [ "id1", "id2" ] })
        tag_backend.inject("ITEMSDONE", {})

        self.compare_flags(ITEMS_ADDED)

        # 4. Getting attributes should cause attributes hook

        self.reset_flags()

        id1_content = { "title" : "id1", "canto-state" : [], "canto-tags" : [],
                "link" : "id1-link", "enclosures" : "" }

        id2_content = { "title" : "id2", "canto-state" : [], "canto-tags" : [],
                "link" : "id2-link", "enclosures" : "" }

        all_content = { "id1" : id1_content, "id2" : id2_content }

        tag_backend.inject("ATTRIBUTES", all_content)

        self.compare_flags(ATTRIBUTES)
        self.compare_var("attributes", all_content)

        id1_got = tag_updater.get_attributes("id1")
        if id1_got != id1_content:
            raise Exception("Bad content: wanted %s - got %s" % (id1_content, id1_got))
        id2_got = tag_updater.get_attributes("id2")
        if id2_got != id2_content:
            raise Exception("Bad content: wanted %s - got %s" % (id2_content, id2_got))

        # 5. Removing an item should *NOT* cause its attributes to be forgotten
        # that happens on stories_removed, and should cause ITEMS_REMOVED

        self.reset_flags()

        tag_backend.inject("ITEMS", { "maintag:Slashdot" : [ "id1" ] })
        tag_backend.inject("ITEMSDONE", {})

        self.compare_flags(ITEMS_REMOVED)

        id2_got = tag_updater.get_attributes("id2")
        if id2_got != id2_content:
            raise Exception("Bad content: wanted %s - got %s" % (id2_content, id2_got))

        # 6. Getting a stories_removed hook should make it forget attributes

        self.reset_flags()
        
        call_hook("curses_stories_removed", [ FakeTag("maintag:Slashdot"), [ FakeStory("id2") ] ])
                
        if "id2" in tag_updater.attributes:
            raise Exception("Expected id2 to be removed, but it isn't!")

        self.compare_flags(0)

        # 7. Getting attributes for non-existent IDs should return empty

        id2_got = tag_updater.get_attributes("id2")
        if id2_got != {}:
            raise Exception("Expected non-existent id to return empty! Got %s" % id2_got)

        self.compare_flags(0)

        # 8. Getting stories_removed for item still in tag should do nothing

        call_hook("curses_stories_removed", [ FakeTag("maintag:Slashdot"), [ FakeStory("id1") ] ])

        if "id1" not in tag_updater.attributes:
            raise Exception("Expected id1 to remain in attributes!")

        self.compare_flags(0)

        # 9. Config adding a tag should create a new tagcore

        config_backend.inject("NEWTAGS", [ "maintag:Test1" ])

        self.compare_flags(NEW_TC)

        # 10. Config removing an empty tag should delete a tagcore

        self.reset_flags()

        config_backend.inject("DELTAGS", [ "maintag:reddit" ])

        self.compare_flags(DEL_TC)
        self.compare_var("del_tc", "maintag:reddit")

        # 11. Config removing an populated tag should delete a tagcore and
        # cause items_removed. NOTE for now tagcores are never deleted, they
        # just exist empty

        self.reset_flags()

        config_backend.inject("DELTAGS", [ "maintag:Slashdot" ])

        self.compare_flags(DEL_TC | ITEMS_REMOVED)
        self.compare_var("del_tc", "maintag:Slashdot")
        self.compare_var("oir_tctag", "maintag:Slashdot")
        self.compare_var("oir_tcids", [ "id1" ])

        # 12. Update should cause all tags to generate a tag_update
        # hook call on ITEMS, and update_complete when all done.

        self.reset_flags()

        tag_updater.update()

        tag_backend.inject("ITEMS", { "maintag:Test1" : [ "id3", "id4" ] })
        tag_backend.inject("ITEMSDONE", {})
        tag_backend.inject("ATTRIBUTES", { "id3" : { "test" : "test" }, "id4" : { "test" : "test" }})

        print(tag_updater.updating)
        self.compare_flags(TAG_UPDATED | UPDATE_COMPLETE | ITEMS_ADDED | ATTRIBUTES)
        self.compare_var("otu_tag", "maintag:Test1")


        return True
示例#40
0
    def prot_configs(self, given, write = False):
        log.debug("prot_configs given:\n%s\n" % json.dumps(given, indent=4, sort_keys=True))

        if "tags" in given:
            for tag in list(given["tags"].keys()):
                ntc = given["tags"][tag]

                tc = self.get_tag_conf(tag)

                changes, deletions =\
                        self.validate_config(ntc, tc, self.tag_validators)

                if changes:
                    self.tag_config[tag] = ntc
                    call_hook("curses_tag_opt_change", [ { tag : changes } ])

                    if write:
                        self.write("SETCONFIGS", { "tags" : { tag : changes }})

                if deletions and write:
                    self.write("DELCONFIGS", { "tags" : { tag : deletions }})

        if "CantoCurses" in given:
            new_config = given["CantoCurses"]

            changes, deletions =\
                    self.validate_config(new_config, self.config,\
                    self.validators)

            if changes:
                self.config = new_config
                call_hook("curses_opt_change", [ changes ])

                if "tags" in changes:
                    self.eval_tags()

                if write:
                    self.write("SETCONFIGS", { "CantoCurses" : changes })

            if deletions and write:
                self.write("DELCONFIGS", { "CantoCurses" : deletions })

        if "defaults" in given:

            changes = {}

            for key in given["defaults"]:
                if key in self.daemon_defaults:
                    if given["defaults"][key] != self.daemon_defaults[key]:
                        changes[key] = given["defaults"][key]
                else:
                    changes[key] = given["defaults"][key]

            self.daemon_defaults.update(changes)

            if write:
                self.write("SETCONFIGS", { "defaults" : self.daemon_defaults })

            call_hook("curses_def_opt_change", [ changes ])

        if "feeds" in given:

            self.daemon_feedconf = given["feeds"]
            if write:
                self.write("SETCONFIGS", { "feeds" : self.daemon_feedconf })

            call_hook("curses_feed_opt_change", [ given["feeds"] ])

        self.initd = True
示例#41
0
    def sync(self, force=False):
        if force or self.tagcore.changes:
            current_stories = []
            added_stories = []

            sel = self.callbacks["get_var"]("selected")

            self.tagcore.lock.acquire_read()

            self.tagcore.ack_changes()

            for story in self:
                if story.id in self.tagcore:
                    current_stories.append(
                        (self.tagcore.index(story.id), story))
                elif story == sel:

                    # If we preserve the selection in an "undead" state, then
                    # we keep set tagcore changed so that the next sync operation
                    # will re-evaluate it.

                    self.tagcore.changed()

                    if current_stories:
                        place = max([x[0] for x in current_stories]) + .5
                    else:
                        place = -1
                    current_stories.append((place, story))

            for place, id in enumerate(self.tagcore):
                if id not in [x[1].id for x in current_stories]:
                    s = Story(self, id, self.callbacks)
                    current_stories.append((place, s))
                    added_stories.append(s)

            self.tagcore.lock.release_read()

            call_hook("curses_stories_added", [self, added_stories])

            conf = config.get_conf()
            if conf["update"]["style"] == "maintain" or self.tagcore.was_reset:
                self.tagcore.was_reset = False
                current_stories.sort()

            current_stories = [x[1] for x in current_stories]

            deleted = []

            for story in self:
                if not story in current_stories:
                    deleted.append(story)
                    story.die()

            # Properly dispose of the remaining stories

            call_hook("curses_stories_removed", [self, deleted])

            del self[:]
            self.extend(current_stories)

            # Trigger a refresh so that classes above (i.e. TagList) will remap
            # items

            self.need_refresh()

        # Pass the sync onto story objects
        for s in self:
            s.sync()

        self.updates_pending = 0
示例#42
0
 def on_del_tag(self, tag):
     for tagcore in alltagcores:
         if tagcore.tag == tag:
             tagcore.reset()
             call_hook("curses_del_tagcore", [ tagcore ])
             return
示例#43
0
    def prot_configs(self, given, write=False):
        log.debug("prot_configs given:\n%s\n",
                  json.dumps(given, indent=4, sort_keys=True))
        if "tags" in given:
            for tag in list(given["tags"].keys()):
                ntc = given["tags"][tag]

                tc = self.get_tag_conf(tag)

                changes, deletions =\
                        self.validate_config(ntc, tc, self.tag_validators)

                if write:
                    if changes:
                        self.wait_write("SETCONFIGS", {"tags": {tag: changes}})
                    if deletions:
                        self.wait_write("DELCONFIGS",
                                        {"tags": {
                                            tag: deletions
                                        }})

                if changes:
                    self.tag_config[tag] = ntc
                    call_hook("curses_tag_opt_change", [{tag: changes}])

        if "CantoCurses" in given:
            new_config = given["CantoCurses"]

            if "config_version" in new_config:
                self.config_version = new_config["config_version"]

            changes, deletions =\
                    self.validate_config(new_config, self.config,\
                    self.validators)

            if "config_version" not in new_config or\
                    new_config["config_version"] != CURRENT_CONFIG_VERSION:

                log.debug("Configuration migrated from %s to %s",\
                        self.config_version, CURRENT_CONFIG_VERSION)

                self.config_version = CURRENT_CONFIG_VERSION
                new_config["config_version"] = CURRENT_CONFIG_VERSION
                changes["config_version"] = CURRENT_CONFIG_VERSION
                self.write("SETCONFIGS", {
                    "CantoCurses": {
                        "config_version": CURRENT_CONFIG_VERSION
                    }
                })

            if write:
                if changes:
                    self.wait_write("SETCONFIGS", {"CantoCurses": changes})

                if deletions:
                    self.wait_write("DELCONFIGS", {"CantoCurses": deletions})

            if changes:
                self.config = new_config
                call_hook("curses_opt_change", [changes])
                if "tags" in changes:
                    self.eval_tags()

        if "defaults" in given:

            changes = {}

            for key in given["defaults"]:
                if key in self.daemon_defaults:
                    if given["defaults"][key] != self.daemon_defaults[key]:
                        changes[key] = given["defaults"][key]
                else:
                    changes[key] = given["defaults"][key]

            self.daemon_defaults.update(changes)

            if write:
                self.wait_write("SETCONFIGS",
                                {"defaults": self.daemon_defaults})

            call_hook("curses_def_opt_change", [changes])

        if "feeds" in given:

            self.daemon_feedconf = given["feeds"]
            if write:
                self.wait_write("SETCONFIGS", {"feeds": self.daemon_feedconf})

            call_hook("curses_feed_opt_change", [given["feeds"]])

        self.initd = True
示例#44
0
    def check(self):
        hooks.on_hook("test", self.hook_a) # No key
        hooks.on_hook("test", self.hook_b, "first_remove")
        hooks.on_hook("test", self.hook_c, "first_remove")
        hooks.on_hook("test2", self.hook_a, "second_remove")

        hooks.call_hook("test", [])

        if self.test_set != "abc":
            raise Exception("Basic hook test failed: %s" % self.test_set)

        self.test_set = ""
        hooks.call_hook("test2", [])

        if self.test_set != "a":
            raise Exception("Basic hook test2 failed: %s" % self.test_set)

        self.test_set = ""
        hooks.unhook_all("first_remove")
        hooks.call_hook("test", [])

        if self.test_set != "a":
            raise Exception("unhook_all failed: %s" % self.test_set)

        self.test_set = ""
        hooks.remove_hook("test", self.hook_a)
        hooks.call_hook("test", [])

        if self.test_set != "":
            raise Exception("remove_hook failed: %s" % self.test_set)

        hooks.call_hook("test2", [])

        if self.test_set != "a":
            raise Exception("improper hook removed: %s" % self.test_set)

        hooks.unhook_all("second_remove")

        if hooks.hooks != {}:
            raise Exception("hooks.hooks should be empty! %s" % hooks.hooks)

        hooks.on_hook("argtest", self.hook_args)

        for args in [ [], ["abc"], [1, 2, 3] ]:
            self.test_args = []
            hooks.call_hook("argtest", args)
            if self.test_args != tuple(args):
                raise Exception("hook arguments failed in %s out %s" % (args, self.test_args)) 

        return True
示例#45
0
 def on_new_tag(self, tag):
     self.prot_tagchange(tag)
     call_hook("curses_new_tagcore", [ TagCore(tag) ])
示例#46
0
    def prot_configs(self, given, write = False):
        log.debug("prot_configs given:\n%s\n", json.dumps(given, indent=4, sort_keys=True))
        if "tags" in given:
            for tag in list(given["tags"].keys()):
                ntc = given["tags"][tag]

                tc = self.get_tag_conf(tag)

                changes, deletions =\
                        self.validate_config(ntc, tc, self.tag_validators)

                if write:
                    if changes:
                        self.wait_write("SETCONFIGS", { "tags" : { tag : changes }})
                    if deletions:
                        self.wait_write("DELCONFIGS", { "tags" : { tag : deletions }})

                if changes:
                    self.tag_config[tag] = ntc
                    call_hook("curses_tag_opt_change", [ { tag : changes } ])

        if "CantoCurses" in given:
            new_config = given["CantoCurses"]

            if "config_version" in new_config:
                self.config_version = new_config["config_version"]

            changes, deletions =\
                    self.validate_config(new_config, self.config,\
                    self.validators)

            if "config_version" not in new_config or\
                    new_config["config_version"] != CURRENT_CONFIG_VERSION:

                log.debug("Configuration migrated from %s to %s",\
                        self.config_version, CURRENT_CONFIG_VERSION)

                self.config_version = CURRENT_CONFIG_VERSION
                new_config["config_version"] = CURRENT_CONFIG_VERSION
                changes["config_version"] = CURRENT_CONFIG_VERSION
                self.write("SETCONFIGS", { "CantoCurses" : {"config_version" : CURRENT_CONFIG_VERSION } })

            if write:
                if changes:
                    self.wait_write("SETCONFIGS", { "CantoCurses" : changes })

                if deletions:
                    self.wait_write("DELCONFIGS", { "CantoCurses" : deletions })

            if changes:
                self.config = new_config
                call_hook("curses_opt_change", [ changes ])
                if "tags" in changes:
                    self.eval_tags()

        if "defaults" in given:

            changes = {}

            for key in given["defaults"]:
                if key in self.daemon_defaults:
                    if given["defaults"][key] != self.daemon_defaults[key]:
                        changes[key] = given["defaults"][key]
                else:
                    changes[key] = given["defaults"][key]

            self.daemon_defaults.update(changes)

            if write:
                self.wait_write("SETCONFIGS", { "defaults" : self.daemon_defaults })

            call_hook("curses_def_opt_change", [ changes ])

        if "feeds" in given:

            self.daemon_feedconf = given["feeds"]
            if write:
                self.wait_write("SETCONFIGS", { "feeds" : self.daemon_feedconf })

            call_hook("curses_feed_opt_change", [ given["feeds"] ])

        self.initd = True
示例#47
0
    def sync(self, force=False):
        if force or self.tagcore.changes:
            sel = self.callbacks["get_var"]("selected")

            self.tagcore.lock.acquire_read()

            self.tagcore.ack_changes()

            # Get sorted ids, along with their (unsorted) positions in the
            # original lists.

            sorted_ids = [(x, x.id, i) for (i, x) in enumerate(self)]
            sorted_ids.sort(key=lambda x: x[1])

            tagcore_sorted_ids = list(enumerate(self.tagcore))
            tagcore_sorted_ids.sort(key=lambda x: x[1])

            new_ids = []
            current_stories = []
            old_stories = []

            for story, s_id, place in sorted_ids:
                while tagcore_sorted_ids and s_id > tagcore_sorted_ids[0][1]:
                    new_ids.append(tagcore_sorted_ids.pop(0))

                if not tagcore_sorted_ids or s_id < tagcore_sorted_ids[0][1]:
                    if sel and (not sel.is_tag) and (s_id == sel.id):

                        # If we preserve the selection in an "undead" state, then
                        # we keep set tagcore changed so that the next sync operation
                        # will re-evaluate it.

                        self.tagcore.changed()
                        place = -1
                        current_stories.append((place, story))
                    else:
                        old_stories.append(story)
                else:
                    place = tagcore_sorted_ids.pop(0)[0]
                    current_stories.append((place, story))

            # Grab any remaining new items
            new_ids += tagcore_sorted_ids

            self.tagcore.lock.release_read()

            new_stories = [(p, Story(self, x, self.callbacks))
                           for (p, x) in new_ids]

            call_hook("curses_stories_added",
                      [self, [x for (p, x) in new_stories]])

            del self[:]

            conf = config.get_conf()
            if conf["update"]["style"] == "maintain" or self.tagcore.was_reset:
                self.tagcore.was_reset = False
                current_stories += new_stories
                current_stories.sort()
                self.extend([x[1] for x in current_stories])
            else:
                current_stories.sort()
                new_stories.sort()
                if conf["update"]["style"] == "append":
                    current_stories += new_stories
                    self.extend([x[1] for x in current_stories])
                else:
                    new_stories += current_stories
                    self.extend([x[1] for x in new_stories])

            for story in old_stories:
                story.die()

            # Properly dispose of the remaining stories

            call_hook("curses_stories_removed", [self, old_stories])

            # Trigger a refresh so that classes above (i.e. TagList) will remap
            # items

            self.need_refresh()

        # Pass the sync onto story objects
        for s in self:
            s.sync()

        self.updates_pending = 0
示例#48
0
    def cmd_sync(self, socket = None, args = None):
        needs_syncto = False

        if not self.sent_config:
            f, fname = mkstemp()
            os.close(f)

            call_hook("daemon_syncfrom", [ "conf", fname ])

            conf_stat = os.stat(self.backend.conf_path)
            sync_stat = os.stat(fname)

            log.debug('conf: %s sync: %s' % (conf_stat.st_mtime, sync_stat.st_mtime))

            diff = sync_stat.st_mtime - conf_stat.st_mtime

            # Will be empty tempfile if syncfrom failed.

            if sync_stat.st_size != 0:
                if diff > 0:
                    log.debug("conf: We are older")
                    parse_locks()
                    shutil.move(fname, self.backend.conf_path)
                    config.parse()
                    parse_unlocks()

                    # Echo these changes to all connected sockets that care
                    for socket in self.backend.watches["config"]:
                        self.backend.in_configs({}, socket)

                elif diff == 0:
                    log.debug("conf: We are equal")
                    os.unlink(fname)
                else:
                    log.debug("conf: We are newer")
                    os.unlink(fname)
                    self.fresh_config = True
                    needs_syncto = True
            else:
                os.unlink(fname)

        if not self.sent_content:
            f, fname = mkstemp()
            os.close(f)

            call_hook("daemon_syncfrom", [ "db", fname ])

            diff = self.time_diff(fname)

            if diff > 0:
                # Lock feeds to make sure nothing's in flight
                wlock_all()

                # Close the file so we can replace it.
                self.backend.shelf.close()

                shutil.move(fname, self.backend.feed_path)

                self.backend.shelf.open()

                # First half of wunlock_all, release these locks so
                # fetch threads can get locks

                for feed in sorted(allfeeds.feeds.keys()):
                    allfeeds.feeds[feed].lock.release_write()

                # Force feeds to be repopulated from disk, which will handle
                # communicating changes to connections

                self.backend.fetch.fetch(True, True)
                self.backend.fetch.reap(True)

                # Complete wunlock_all()
                feed_lock.release_write()

            # Equal, just clear it up

            elif diff == 0:
                os.unlink(fname)

            # If we're actually newer on a syncfrom then make syncto happen
            # next time. This can happen on init.

            else:
                os.unlink(fname)
                self.fresh_content = True
                needs_syncto = True

        if needs_syncto:
            self.cmd_syncto()

        self.reset()
示例#49
0
 def prot_attributes(self, d):
     call_hook("curses_attributes", [ d ])
示例#50
0
    def cmd_sync(self, socket=None, args=None):
        needs_syncto = False

        if not self.sent_config:
            f, fname = mkstemp()
            os.close(f)

            call_hook("daemon_syncfrom", ["conf", fname])

            conf_stat = os.stat(self.backend.conf_path)
            sync_stat = os.stat(fname)

            log.debug('conf: %s sync: %s' %
                      (conf_stat.st_mtime, sync_stat.st_mtime))

            diff = sync_stat.st_mtime - conf_stat.st_mtime

            # Will be empty tempfile if syncfrom failed.

            if sync_stat.st_size != 0:
                if diff > 0:
                    log.debug("conf: We are older")
                    parse_locks()
                    shutil.move(fname, self.backend.conf_path)
                    config.parse()
                    parse_unlocks()

                    # Echo these changes to all connected sockets that care
                    for socket in self.backend.watches["config"]:
                        self.backend.in_configs({}, socket)

                elif diff == 0:
                    log.debug("conf: We are equal")
                    os.unlink(fname)
                else:
                    log.debug("conf: We are newer")
                    os.unlink(fname)
                    self.fresh_config = True
                    needs_syncto = True
            else:
                os.unlink(fname)

        if not self.sent_content:
            f, fname = mkstemp()
            os.close(f)

            call_hook("daemon_syncfrom", ["db", fname])

            diff = self.time_diff(fname)

            if diff > 0:
                # Lock feeds to make sure nothing's in flight
                wlock_all()

                # Close the file so we can replace it.
                self.backend.shelf.close()

                shutil.move(fname, self.backend.feed_path)

                self.backend.shelf.open()

                # Clear out all of the currently tagged items. Usually on
                # update, we're able to discard items that we have in old
                # content, but aren't in new. But since we just replaced all of
                # our old content with a totally fresh copy, we might not know
                # they exist. Can't use reset() because we don't want to lose
                # configuration.

                alltags.clear_tags()

                # First half of wunlock_all, release these locks so
                # fetch threads can get locks

                for feed in sorted(allfeeds.feeds.keys()):
                    allfeeds.feeds[feed].lock.release_write()

                # Complete wunlock_all()
                feed_lock.release_write()

                # Force feeds to be repopulated from disk, which will handle
                # communicating changes to connections

                self.backend.fetch.fetch(True, True)
                self.backend.fetch.reap(True)

            # Equal, just clear it up

            elif diff == 0:
                os.unlink(fname)

            # If we're actually newer on a syncfrom then make syncto happen
            # next time. This can happen on init.

            else:
                os.unlink(fname)
                self.fresh_content = True
                needs_syncto = True

        if needs_syncto:
            self.cmd_syncto()

        self.reset()
示例#51
0
    def check(self):
        config_script = {
            'VERSION': {
                '*': [('VERSION', CANTO_PROTOCOL_COMPATIBLE)]
            },
            'CONFIGS': {
                '*': [('CONFIGS', {
                    "CantoCurses": config.template_config
                })]
            },
        }

        config_backend = TestBackend("config", config_script)

        config.init(config_backend, CANTO_PROTOCOL_COMPATIBLE)

        config_backend.inject("NEWTAGS",
                              ["maintag:Slashdot", "maintag:reddit"])

        tagcore_script = {}

        tag_backend = TestBackend("tagcore", tagcore_script)

        on_hook("curses_items_removed", self.on_items_removed)
        on_hook("curses_items_added", self.on_items_added)
        on_hook("curses_new_tagcore", self.on_new_tagcore)
        on_hook("curses_del_tagcore", self.on_del_tagcore)
        on_hook("curses_attributes", self.on_attributes)
        on_hook("curses_update_complete", self.on_update_complete)
        on_hook("curses_tag_updated", self.on_tag_updated)

        # 1. Previously existing tags in config should be populated on init

        self.reset_flags()

        tag_updater.init(tag_backend)

        for tag in config.vars["strtags"]:
            for tc in alltagcores:
                if tc.tag == tag:
                    break
            else:
                raise Exception("Couldn't find TC for tag %s" % tag)

        self.compare_flags(NEW_TC)

        self.reset_flags()

        # 2. Getting empty ITEMS responses should cause no events

        tag_backend.inject("ITEMS", {"maintag:Slashdot": []})
        tag_backend.inject("ITEMSDONE", {})

        tag_backend.inject("ITEMS", {"maintag:reddit": []})
        tag_backend.inject("ITEMSDONE", {})

        self.compare_flags(0)

        # 3. Getting a non-empty ITEMS response should cause items_added

        tag_backend.inject("ITEMS", {"maintag:Slashdot": ["id1", "id2"]})
        tag_backend.inject("ITEMSDONE", {})

        self.compare_flags(ITEMS_ADDED)

        # 4. Getting attributes should cause attributes hook

        self.reset_flags()

        id1_content = {
            "title": "id1",
            "canto-state": [],
            "canto-tags": [],
            "link": "id1-link",
            "enclosures": ""
        }

        id2_content = {
            "title": "id2",
            "canto-state": [],
            "canto-tags": [],
            "link": "id2-link",
            "enclosures": ""
        }

        all_content = {"id1": id1_content, "id2": id2_content}

        tag_backend.inject("ATTRIBUTES", all_content)

        self.compare_flags(ATTRIBUTES)
        self.compare_var("attributes", all_content)

        id1_got = tag_updater.get_attributes("id1")
        if id1_got != id1_content:
            raise Exception("Bad content: wanted %s - got %s" %
                            (id1_content, id1_got))
        id2_got = tag_updater.get_attributes("id2")
        if id2_got != id2_content:
            raise Exception("Bad content: wanted %s - got %s" %
                            (id2_content, id2_got))

        # 5. Removing an item should *NOT* cause its attributes to be forgotten
        # that happens on stories_removed, and should cause ITEMS_REMOVED

        self.reset_flags()

        tag_backend.inject("ITEMS", {"maintag:Slashdot": ["id1"]})
        tag_backend.inject("ITEMSDONE", {})

        self.compare_flags(ITEMS_REMOVED)

        id2_got = tag_updater.get_attributes("id2")
        if id2_got != id2_content:
            raise Exception("Bad content: wanted %s - got %s" %
                            (id2_content, id2_got))

        # 6. Getting a stories_removed hook should make it forget attributes

        self.reset_flags()

        call_hook("curses_stories_removed",
                  [FakeTag("maintag:Slashdot"), [FakeStory("id2")]])

        if "id2" in tag_updater.attributes:
            raise Exception("Expected id2 to be removed, but it isn't!")

        self.compare_flags(0)

        # 7. Getting attributes for non-existent IDs should return empty

        id2_got = tag_updater.get_attributes("id2")
        if id2_got != {}:
            raise Exception(
                "Expected non-existent id to return empty! Got %s" % id2_got)

        self.compare_flags(0)

        # 8. Getting stories_removed for item still in tag should do nothing

        call_hook("curses_stories_removed",
                  [FakeTag("maintag:Slashdot"), [FakeStory("id1")]])

        if "id1" not in tag_updater.attributes:
            raise Exception("Expected id1 to remain in attributes!")

        self.compare_flags(0)

        # 9. Config adding a tag should create a new tagcore

        config_backend.inject("NEWTAGS", ["maintag:Test1"])

        self.compare_flags(NEW_TC)

        # 10. Config removing an empty tag should delete a tagcore

        self.reset_flags()

        config_backend.inject("DELTAGS", ["maintag:reddit"])

        self.compare_flags(DEL_TC)
        self.compare_var("del_tc", "maintag:reddit")

        # 11. Config removing an populated tag should delete a tagcore and
        # cause items_removed. NOTE for now tagcores are never deleted, they
        # just exist empty

        self.reset_flags()

        config_backend.inject("DELTAGS", ["maintag:Slashdot"])

        self.compare_flags(DEL_TC | ITEMS_REMOVED)
        self.compare_var("del_tc", "maintag:Slashdot")
        self.compare_var("oir_tctag", "maintag:Slashdot")
        self.compare_var("oir_tcids", ["id1"])

        # 12. Update should cause all tags to generate a tag_update
        # hook call on ITEMS, and update_complete when all done.

        self.reset_flags()

        tag_updater.update()

        tag_backend.inject("ITEMS", {"maintag:Test1": ["id3", "id4"]})
        tag_backend.inject("ITEMSDONE", {})
        tag_backend.inject("ATTRIBUTES", {
            "id3": {
                "test": "test"
            },
            "id4": {
                "test": "test"
            }
        })

        print(tag_updater.updating)
        self.compare_flags(TAG_UPDATED | UPDATE_COMPLETE | ITEMS_ADDED
                           | ATTRIBUTES)
        self.compare_var("otu_tag", "maintag:Test1")

        return True