Posts about programming (old posts, page 17)

2008-07-25 12:55

Urssus: July 25th - approaching parity

My stated goal of Akregator feature parity is looking even better. With screenshot!

urssus7

If you didn't read (or care about) last night's updates to yesterday's post, here they are:

  • Implemented "Mark posts as read on arrival for this feed"
  • Implemented customupdate intervals for feeds
  • Fixed article filtering
  • Implemented filtering by article status
  • Implemented folder-as-feed (not perfect)
  • Export as OPML

And today I did these:

  • Switched from Mako to Tenjin (which I can just bundle)
  • Implemented "Combined View" mode (you can see it in the screenshot above)
  • Implemented "Import Technorati top 10"
  • Organized the code correctly
  • Wrote a Paver script so it can be installed nicely (someday)
  • Removed all generated files from the repo (now you generate them using paver and/or setup.py)

So, on the TODO we have:

  • Favicons
  • Implement app preferences dialog
  • Bugfixes
  • Pagination for combinedView (so you don't have a page with 90000 posts in it which takes a month to load)
  • Akregator DB import script, so I can compare performances reasonably.

And still fun!

Current LOC count: 1295

2008-07-24 13:49

Urssus: July 24th

When I started this project I wrote:

I intend to keep working like this for a couple of weeks, and see how far I can get in feature parity to akregator.

No, I don't expect to reach feature parity, I only want to strive for it. SInce I lack the focusand/or energy for a multi year commitment it requires to write the average free software, I want to see how far a sprint gets me.

To my surprise, only 9 days into the project, feature parity is at hand.

What's not done?

  • Some feed properties (custom archiving modes, mark as read on arrival) (Update: "mark as readon arrival" implemented at 22:40)
  • Fix article filtering (it's not quite broken, but interacts badly with many things) (Update: done as of 19:20)
  • Implement app preferences dialog
  • Filtering by article status (Update: implemented as of 19:01)
  • Folder-as-feed (Update: done, as of 22:20, even if not really efficiently)
  • Export as OPML (Update: just implemented it :-)
  • Favicons

And some things in the backend, which are not proper features.

What have I done today?

  • New articles notification via systray
  • Custom update intervals per-feed
  • Made Feed.unreadCount() fast enough
  • Article sorting (not as trivial as it sounds)
  • Fixed a lot of bugs (none of which crashed uRSSus, take that Akregator! ;-)

And still I have written only 1066 lines of code, according to sloccount.

Update: 1127 lines with the extra features marked above as "update"

2008-07-23 09:29

Urssus: July 23rd (early)

So I did work on it a little last night:

  • Feed/Folder context menus
  • Open Feed Homepage action
  • New Feed (uses Mark Pilgrim's feedfinder.py, which is awesome)
  • New Folder
  • Delete Feed/Folder
  • Some UI fixes (finally figured out how to make a QTreeView's headers look right!)
  • Some bugfixes in tree traversing (where else ;-)
  • Implemented "fetch the web page instead of the feed contents" option for feeds (because of your annoying feed, DZone!)

Sadly no visible changes, so a screenshot would be useless.

2008-07-22 16:48

Urssus: July 22nd / looking good

Today it was a bit more than two hours, but spread in 10 minute chunks, which is not exactly efficient.

Improvements:

  • Rewrote the whole next/next unread/article/feed spaghetti in a decent way. It's broken regarding filtered/sorted article lists, but it's trivial to fix.
  • A few UI features:
    • Show only feeds with unread articles google-reader style
    • Show in dark red the unread articles
    • Show title / date of articles in separate columns
  • Implemented "Match case" for the search
  • Implemented dumb systray icon (need to make it useful)
  • Implemented article counts in folders (but not article lists which is, of course, harder)

And of course, a screenshot:

urssus5

A bad side is that the main window takes about 3 seconds to appear, but I am pretty sure that's fixable reordering the startup code.

TODO:

  • Feed CRUD
  • Post mark as (read/unread/important/whatever)
  • Filter by post status
  • Search in all feeds
  • Virtual feeds
  • Persistent settings
  • More aggressive feed fetching (now it's a single queue, should be a configurable number)
  • Lots of minor fixes

And it will be pretty much in feature parity with Akregator, and ready to start the packaging work. Not bad for a week of work (BTW: if anyone has any experience packaging PyQt stuff for windows/Mac I can use the help ;-).

Still fun (although tree traversing is starting to get quite annoying)

2008-07-20 17:44

Urssus: July 20th / eating my own dogfood

Yes, I am now using uRSSus instead of Akregator.

On the programming side, I did very little work because it's sunday...

  • Implemented the Find windget backend code and it works forwad and backward (but not "Match case" which is pretty trivial).
  • Fixed a few focus issues. When you have a multi-widget main window, it can get kinda tricky, and you may end needing to move focus around manually (not in this case yet).

I have found a few bugs, mainly related to traversing the feed tree, but they are all fixable with some effort (or by doing it correctly the second time ;-)

2008-07-19 08:43

Urssus: July 19th

I confess I cheated and kept working on it yesterday after the blog post. OTOH, I will not touch it today ;-). Big functionality added, too.

  • Feed items (The things on the tree) now are updated when the background process checks them.
  • They are also updated when you read articles from a feed.
  • The filter thingie works, you type some text, and only the articles with that text are shown (see screenshot).
  • Added a widget (not dialog) for searching within the page, firefox-like (see screenshot)
urssus4

The bad news is that the "next unread article" code and a few others is garbage. It's quite inefficient because I tried to be cheap and not create a coherent model for feeds.

However it works and you will never tell the difference unless you have 2000 articles between where you are and the next unread (in which case the window goes kinda nuts for a couple of seconds).

Still fun!

2008-07-18 15:43

Urssus again

Another day, another two hours of work on it.

What's new?

  • Some UI elements without code behind them (the "filter article" thingie), and a feed properties dialog.
  • A (IMVHO) better UI distribution than akregator. Consider this screenshot:
urssus3

The article list and feed tree are the same size, but removing the tabs and moving the filter into a toolbar really makes the actual reading area quite larger.

  • Delegate all links from the reading area to the desktop's web browser (this is not a web browser yet ;-)
  • Progress report for web page loading (more useful once you can declare a feed as "load link directly")
  • Finished feed/article navigation (next/previous feed/article unread/any), and fixed bugs in the part that was already done.
  • Zoom in/out for the web view
  • Show/hide statusbar
  • Several bug fixes

And yes, still fun!

2008-07-17 16:21

Urssus improves

Today's 2 hours:

  • An about dialog (not wired to the UI yet)
  • Icons on the feed tree (not favicons, though)
  • Handle a few more feed quirks
  • A status message queue, so subprocesses can post their progress in the status bar.
  • UI for importOPML
  • Implemented "Next Article" and "Next Feed". This Model/View Qt thing is kinda painful (but powerful!)
  • Use of Mako templates to display the articles pretty and neat
  • Menu & Shortcuts following Akreggator
  • Added several fields to the Post and Feed models to make them more useful (which means the DB changed, but that's to be expected at this stage). These include things like "unread" and "author" and "link" ;-)

Still fun!

2008-07-16 21:44

A programming challenge for myself

I worked on uRSSus for a couple of hours again, and it's working pretty nicely.

  • Really uses the ORM
  • Multiprocessing for a non-blocking UI (python-processing is awesome)
  • Adapts to the quirks of some feeds (why on earth would someone do a feed without dates? It's AOL FanHouse!)

I intend to keep working like this for a couple of weeks, and see how far I can get in feature parity to akregator.

No, I don't expect to reach feature parity, I only want to strive for it. SInce I lack the focusand/or energy for a multi year commitment it requires to write the average free software, I want to see how far a sprint gets me.

urssus2

So far, it's fun.

2008-07-15 23:38

The world lamest GUI newsreader... in 130 LOC

I started this as an experiment to see how hard it was to build apps using QT and Elixir. This is how it looks after two hours of coding:

urssus1

And here's the code: http://urssus.googlecode.com

You will need:

  • PyQt 4.4 or later
  • Mark Pilgrim's feedparser
  • Python 2.5 (or whenever elementtree got included)
  • A OPML file
  • The Python SQLite bindings
  • Elixir (the declarative layer over SQL Alchemy)

You can find the real code at

And then you can use these 131 LOC ;-)

# -*- coding: utf-8 -*-

# Mark Pilgrim's feed parser
import feedparser as fp

# DB Classes
from elixir import *

metadata.bind = "sqlite:///urssus.sqlite"
metadata.bind.echo = True

class Feed(Entity):
  htmlUrl     = Field(Text)
  xmlUrl      = Field(Text)
  title       = Field(Text)
  text        = Field(Text)
  description = Field(Text)
  children    = OneToMany('Feed')
  parent      = ManyToOne('Feed')
  posts       = OneToMany('Post')
  def __repr__(self):
    return self.text

  def update(self):
    d=fp.parse(self.xmlUrl)

class Post(Entity):
  feed        = ManyToOne('Feed')
  title       = Field(Text)
  post_id     = Field(Text)
  content     = Field(Text)

# This is just temporary
setup_all()
create_all()

# UI Classes
from PyQt4 import QtGui, QtCore
from Ui_main import Ui_MainWindow

class MainWindow(QtGui.QMainWindow):
  def __init__(self):
    QtGui.QMainWindow.__init__(self)

    # Set up the UI from designer
    self.ui=Ui_MainWindow()
    self.ui.setupUi(self)

    # Initialize the tree from the Feeds
    self.model=QtGui.QStandardItemModel()

    # Internal function
    def addSubTree(parent, node):
      nn=QtGui.QStandardItem(unicode(node))
      parent.appendRow(nn)
      nn.feed=node
      if not node.children:
        return
      else:
        for child in node.children:
          addSubTree(nn, child)

    roots=Feed.query.filter(Feed.parent==None)
    iroot=self.model.invisibleRootItem()
    iroot.feed=None
    for root in roots:
      addSubTree(iroot, root)

    self.ui.feeds.setModel(self.model)

    QtCore.QObject.connect(self.ui.feeds, QtCore.SIGNAL("clicked(QModelIndex)"), self.openFeed)
    QtCore.QObject.connect(self.ui.posts, QtCore.SIGNAL("clicked(QModelIndex)"), self.openPost)

  def openFeed(self, index):
    item=self.model.itemFromIndex(index)
    feed=item.feed

    if not feed.xmlUrl:
      return

    d=fp.parse(feed.xmlUrl)
    posts=[]
    for post in d['entries']:
      print post
      print '----------------------------------\n\n'
      if 'content' in post:
        posts.append(Post(feed=feed, title=post['title'], post_id=post['id'], content='<hr>'.join([ c.value for c in post['content']])))
      elif 'summary' in post:
        posts.append(Post(feed=feed, title=post['title'], post_id=post['id'], content=post['summary']))
      elif 'value' in post:
        posts.append(Post(feed=feed, title=post['title'], post_id=post['id'], content=post['value']))
    session.flush()

    self.ui.posts.__model=QtGui.QStandardItemModel()
    for post in posts:
      item=QtGui.QStandardItem(post.title)
      item.post=post
      self.ui.posts.__model.appendRow(item)
    self.ui.posts.setModel(self.ui.posts.__model)

  def openPost(self, index):
    item=self.ui.posts.__model.itemFromIndex(index)
    post=item.post
    self.ui.view.setHtml(post.content)

if __name__ == "__main__":
  import sys
  # For starters, lets import a OPML file into the DB so we have some data to work with
  from xml.etree import ElementTree
  tree = ElementTree.parse(sys.argv[1])
  current=None
  for outline in tree.findall("//outline"):
    xu=outline.get('xmlUrl')
    if xu:
      f=Feed(xmlUrl=outline.get('xmlUrl'),
             htmlUrl=outline.get('htmlUrl'),
             title=outline.get('title'),
             text=outline.get('text'),
             description=outline.get('description')
             )
      if current:
        current.children.append(f)
        f.parent=current
    else:
      current=Feed(text=outline.get('text'))
    session.flush()
  app=QtGui.QApplication(sys.argv)
  window=MainWindow()
  window.show()
  sys.exit(app.exec_())

Contents © 2000-2019 Roberto Alsina