Implement update.style
authorJack Miller <jack@codezen.org>
Wed, 22 Jun 2011 19:26:25 +0000 (14:26 -0500)
committerJack Miller <jack@codezen.org>
Wed, 22 Jun 2011 19:26:25 +0000 (14:26 -0500)
update.style can be set to "maintain" or "append"

Append mode is the default, and has been since the beginning. In this
mode, new items are appended to the end of the tag. This is nice because
it allows the user to know that all items above the cursor have been
read without necessarily having to set them as read as he progresses.
The downside is that updates can leave a tag out of expected order.
(ABCD -> update -> ABCDAB instead of AABBCD).

Maintain mode is a new addition that will reorder the items based on the
order that they were given by the daemon. This means new items may
appear above the cursor, but it also means that sort order is maintained
(thus the name).

Signed-off-by: Jack Miller <jack@codezen.org>
canto_curses/gui.py
canto_curses/tag.py

index 847980e..27efc0f 100644 (file)
@@ -109,6 +109,7 @@ Press [space] to close."""
             "tags" : r"maintag\\:.*",
             "tagorder" : [],
 
+            "update.style" : "append",
             "update.auto.interval" : 60,
 
             "reader.maxwidth" : 0,
@@ -389,22 +390,33 @@ Press [space] to close."""
         else:
             config[attr] = r
 
+    def _val_update_style(self, config, defconfig, attr):
+        if config[attr] not in [ "maintain", "append"]:
+            log.error("Couldn't parse %s as update.style" % config[attr])
+            config[attr] = defconfig[attr]
+
     def validate_config(self, newconfig, defconfig):
         self._val_uint(newconfig, defconfig, "update.auto.interval")
+        self._val_update_style(newconfig, defconfig, "update.style")
+
         self._val_bool(newconfig, defconfig, "reader.show_description")
         self._val_bool(newconfig, defconfig, "reader.enumerate_links")
+
         self._val_bool(newconfig, defconfig, "story.enumerated")
+
         self._val_bool(newconfig, defconfig, "taglist.tags_enumerated")
         self._val_bool(newconfig, defconfig,\
                 "taglist.tags_enumerated_absolute")
         self._val_bool(newconfig, defconfig, "taglist.hide_empty_tags")
-        self._val_bool(newconfig, defconfig, "txt_browser")
-        self._val_tag_order(newconfig, defconfig, "tagorder")
         self._val_non_empty_string_list(newconfig,\
                 defconfig, "taglist.search_attributes")
+
+        self._val_bool(newconfig, defconfig, "txt_browser")
+
         self._val_non_empty_string_list(newconfig,\
                 defconfig, "story.format.attrs")
 
+        self._val_tag_order(newconfig, defconfig, "tagorder")
         # Make sure colors are all integers.
         for attr in [k for k in newconfig.keys() if k.startswith("color.")]:
             self._val_color(newconfig, defconfig, attr)
@@ -593,48 +605,58 @@ Press [space] to close."""
 
         for tag in updates:
             for have_tag in self.vars["alltags"]:
-                if have_tag.tag == tag:
-                    adds = []
-                    removes = []
+                if have_tag.tag != tag:
+                    continue
+
+                adds = []
+                removes = []
+
+                # Eliminate discarded items.
+                for id in have_tag.get_ids():
+                    if id not in self.vars["protected_ids"] and \
+                            id not in updates[tag]:
+                        removes.append(id)
+
+                # Add new items.
+                for id in updates[tag]:
+                    if id not in have_tag.get_ids():
+                        adds.append(id)
 
-                    # Eliminate discarded items.
-                    for id in have_tag.get_ids():
-                        if id not in self.vars["protected_ids"] and \
-                                id not in updates[tag]:
-                            removes.append(id)
+                have_tag.add_items(adds)
+                for id in adds:
+                    story = have_tag.get_id(id)
 
-                    # Add new items.
-                    for id in updates[tag]:
-                        if id not in have_tag.get_ids():
-                            adds.append(id)
+                    # We *at least* need title, state, and link, these
+                    # will allow us to fall back on the default format string
+                    # which relies on these.
 
-                    have_tag.add_items(adds)
-                    for id in adds:
-                        story = have_tag.get_id(id)
+                    needed_attrs[id] = [ "title", "canto-state", "link" ]
 
-                        # We *at least* need title, state, and link, these
-                        # will allow us to fall back on the default format string
-                        # which relies on these.
+                    # Make sure we grab attributes needed for the story
+                    # format and story format.
 
-                        needed_attrs[id] = [ "title", "canto-state", "link" ]
+                    for attrlist in ["story.format.attrs",\
+                                        "taglist.search_attributes"]:
+                        for sa in self.config[attrlist]:
+                            if sa not in needed_attrs[id]:
+                                needed_attrs[id].append(sa)
 
-                        # Make sure we grab attributes needed for the story
-                        # format and story format.
+                have_tag.remove_items(removes)
+                for id in removes:
+                    unprotect["auto"].append(id)
 
-                        for attrlist in ["story.format.attrs",\
-                                            "taglist.search_attributes"]:
-                            for sa in self.config[attrlist]:
-                                if sa not in needed_attrs[id]:
-                                    needed_attrs[id].append(sa)
+                # If we're using the maintain update style, reorder the feed
+                # properly. Append style requires no extra work (add_items does
+                # it by default).
 
-                    have_tag.remove_items(removes)
-                    for id in removes:
-                        unprotect["auto"].append(id)
+                if self.config["update.style"] == "maintain":
+                    log.debug("Re-ording items (update.style: maintain)")
+                    have_tag.reorder(updates[tag])
 
         if needed_attrs:
             self.backend.write("ATTRIBUTES", needed_attrs)
 
-        if unprotect:
+        if unprotect["auto"]:
             self.backend.write("UNPROTECT", unprotect)
 
     def prot_tagchange(self, tag):
@@ -733,9 +755,22 @@ Press [space] to close."""
 
             if tweak in [ "selected", "reader_item" ]:
                 if self.vars[tweak] and hasattr(self.vars[tweak], "id"):
+
+                    # protected_ids just tells the prot_items to not allow
+                    # this item to have it's auto protection stripped.
+
                     self.vars["protected_ids"].remove(self.vars[tweak].id)
+
+                    # Set an additional protection, filter-immune so hardened
+                    # filters won't eliminate it.
+
+                    self.backend.write("UNPROTECT",\
+                            { "filter-immune" : [ self.vars[tweak].id ] })
+
                 if value and hasattr(value, "id"):
                     self.vars["protected_ids"].append(value.id)
+                    self.backend.write("PROTECT",\
+                            { "filter-immune" : [ value.id ] })
 
             self.vars[tweak] = value
 
index d50b11d..6e4671c 100644 (file)
@@ -105,6 +105,40 @@ class Tag(list):
 
         call_hook("items_added", [ self, added ] )
 
+    # Take a list of ordered ids and reorder ourselves, without generating any
+    # unnecessary add/remove hooks.
+
+    def reorder(self, ids):
+        cur_stories = [ s for s in self ]
+
+        # Perform the actual reorder.
+        stories = [ self.get_id(id) for id in ids ]
+
+        del self[:]
+        list.extend(self, stories)
+
+        # Deal with items that aren't listed. Usually this happens if the item
+        # would be filtered, but is protected for some reason (like selection)
+
+        # NOTE: This is bad behavior, but if we don't retain these items, other
+        # code will crap-out expecting this item to exist. Built-in transforms
+        # are hardened to never discard items with the filter-immune reason,
+        # like selection, so this is just for bad user transforms.
+
+        for s in cur_stories:
+            if s not in self:
+                log.warn("Warning: A filter is filtering filter-immune items.")
+                log.warn("Compensating. This may cause items to jump unexpectedly.")
+                list.append(self, s)
+
+        log.debug("Self: %s" % [ s for s in self ])
+
+        # Handle updating story information.
+        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)
+
     # Remove Story based on ID
 
     def remove_items(self, ids):