So I did work on it a little last night:
Sadly no visible changes, so a screenshot would be useless.
Today it was a bit more than two hours, but spread in 10 minute chunks, which is not exactly efficient.
Improvements:
And of course, a screenshot:
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:
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)
Yes, I am now using uRSSus instead of Akregator.
On the programming side, I did very little work because it's sunday...
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 ;-)
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.
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!
Another day, another two hours of work on it.
What's new?
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.
And yes, still fun!
Today's 2 hours:
Still fun!
I worked on uRSSus for a couple of hours again, and it's working pretty nicely.
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.
So far, it's fun.
When you upgrade a piece of software on Linux, there are two paths it can go when there are incompatible changes in the config files (ok, 3 paths, Debian asks you what to do):
This has two problems:
The software may fail or not, or fail in a subtle way, and you will not notice right away.
Maybe the old file will be lost anyway:
lrwxrwxrwx 1 root root 32 jul 15 22:41 /etc/named.conf -> /var/named/chroot/etc/named.conf lrwxrwxrwx 1 root root 32 jul 15 22:36 /etc/named.conf.rpmsave -> /var/named/chroot/etc/named.conf
In this case the "file" was a symlink, so by "saving a copy" it only saved another symlink to the soon-to-be-overwritten file.
And that's why, ladies and gentlemen, the rpmnew way is the good way.
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:
And here's the code: http://urssus.googlecode.com
You will need:
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_())
This blog was hosted for a long time in http://lateral.pycs.net ... until the site kinda died. And there are hundreds of links around, all pointing to URLs in pycs.net for the older stories. Now they all work again!
So, thanks Philip!