diff --git a/TODO b/TODO index 9c926ac..c473fa7 100644 --- a/TODO +++ b/TODO @@ -14,6 +14,6 @@ features: bugs: * Direct Messages have no names, only screen names (may not be fixable without - considerable tweets to python-twitter) -* Can't always kill tabs -* New segfault at start that can't be reproduced with gdb. Must be a timing issue... adding locked access to some data in TweetPane didn't help... need locks to go with api objects. + considerable tweaks to python-twitter) +* Can't always kill tabs - open tab list not always correctly populated? +* Can't always kill application diff --git a/apithreads.py b/apithreads.py index 1b22523..baa1e95 100644 --- a/apithreads.py +++ b/apithreads.py @@ -2,7 +2,19 @@ import re import gtk -from threading import Thread +from threading import Thread,RLock +from twitter import Api + +class SafeApi(Api): + ''' This is just a Twitter API with an RLock for multi-threaded access ''' + + def __init__(self, username, password): + Api.__init__(self, username, password) + self.lock = RLock() + +# End class SafeApi + + class GetTweets(Thread): def __init__(self, api, list_name, pane, num_entries, username): @@ -15,33 +27,35 @@ class GetTweets(Thread): def run(self): - # username/Home entries need to load the appropriate Home feed - if self.list_name == self.username + '/Home': - statuses = self.api.GetHomeTimeline(count=self.num_entries) + with self.api.lock: - # For @username, check if it is one of our usernames, or - # just needs to be searched on - elif self.list_name == '@' + self.username: - statuses = self.api.GetMentions(count=self.num_entries) - elif re.match('@', self.list_name): - statuses = results_to_statuses(self.api.Search(self.list_name, rpp=self.num_entries)) + # username/Home entries need to load the appropriate Home feed + if self.list_name == self.username + '/Home': + statuses = self.api.GetHomeTimeline(count=self.num_entries) - # Direct Messages should match like /Home, above - elif self.list_name == self.username + '/Direct Messages': - statuses = dms_to_statuses(self.api.GetDirectMessages()) + # For @username, check if it is one of our usernames, or + # just needs to be searched on + elif self.list_name == '@' + self.username: + statuses = self.api.GetMentions(count=self.num_entries) + elif re.match('@', self.list_name): + statuses = results_to_statuses(self.api.Search(self.list_name, rpp=self.num_entries)) - # User lookups go straight to the user - elif re.match(r'user: ', self.list_name): - statuses = self.api.GetUserTimeline(re.sub(r'^user: ', r'', self.list_name), count=self.num_entries) + # Direct Messages should match like /Home, above + elif self.list_name == self.username + '/Direct Messages': + statuses = dms_to_statuses(self.api.GetDirectMessages()) - # Lists load the appropriate list from the appropriate account - elif re.match(r'list: ', self.list_name): - real_list = re.sub(r'list: .*/(.*)', r'\1', self.list_name) - statuses = self.api.GetListStatuses(real_list, per_page=self.num_entries) + # User lookups go straight to the user + elif re.match(r'user: ', self.list_name): + statuses = self.api.GetUserTimeline(re.sub(r'^user: ', r'', self.list_name), count=self.num_entries) - # Everything else is a straight search - else: - statuses = results_to_statuses(self.api.Search(self.list_name, rpp=self.num_entries)) + # Lists load the appropriate list from the appropriate account + elif re.match(r'list: ', self.list_name): + real_list = re.sub(r'list: .*/(.*)', r'\1', self.list_name) + statuses = self.api.GetListStatuses(real_list, per_page=self.num_entries) + + # Everything else is a straight search + else: + statuses = results_to_statuses(self.api.Search(self.list_name, rpp=self.num_entries)) gtk.gdk.threads_enter() try: @@ -64,7 +78,8 @@ class GetSingleTweet(Thread): def run(self): statuses = [] - statuses.append(self.api.GetStatus(self.single_tweet)) + with self.api.lock: + statuses.append(self.api.GetStatus(self.single_tweet)) gtk.gdk.threads_enter() try: @@ -89,7 +104,8 @@ class GetFollowing(Thread): screen_name = re.sub('user: ', '', self.user) try: - relationship = self.api.ShowFriendships(target_screen_name=screen_name) + with self.api.lock: + relationship = self.api.ShowFriendships(target_screen_name=screen_name) following = relationship.source.following except HTTPError: following = false @@ -112,7 +128,8 @@ class GetVerified(Thread): screen_name = re.sub('user: ', '', self.user) try: - user = self.api.GetUser(screen_name) + with self.api.lock: + user = self.api.GetUser(screen_name) verified = user.verified except HTTPError: verified = false diff --git a/mytwitter.py b/mytwitter.py index 3dbc989..6535411 100755 --- a/mytwitter.py +++ b/mytwitter.py @@ -3,7 +3,6 @@ # Custom twitter client... mostly for learning Python import sys, ConfigParser, os, re, optparse, shelve -import twitter import gtk, gtk.glade, gobject from urllib2 import HTTPError from twitterwidgets import TweetPane @@ -47,7 +46,7 @@ class MyTwitter(): for item in config.sections(): if (re.match(r'account', item)): username = config.get(item, 'username') - self.accounts[username] = twitter.Api(username=username, password=config.get(item, 'password')) + self.accounts[username] = apithreads.SafeApi(username=username, password=config.get(item, 'password')) self.username = self.accounts.keys()[0] self.api = self.accounts[self.username] @@ -106,8 +105,9 @@ class MyTwitter(): # Add the tabs from last session to the notebook page_num = self.db['active_page'] for tab, single_tweet in self.db['open_tabs']: - self.add_to_notebook(tab, single_tweet) + self.add_to_notebook(tab, single_tweet, update=False) self.tweet_notebook.set_current_page(page_num) + self.update_windows() # Put Home, @user, Direct Messages, and lists in the View menu for # each user @@ -188,7 +188,8 @@ class MyTwitter(): def update_status(self): text = self.update_entry.get_text() self.update_entry.set_text("") - self.api.PostUpdate(text, in_reply_to_status_id=self.reply_id) + with self.api.lock: + self.api.PostUpdate(text, in_reply_to_status_id=self.reply_id) self.reply_id = None self.update_status_bar('Tweet Posted') @@ -224,7 +225,8 @@ class MyTwitter(): def on_retweet(self, widget, data): - self.api.PostRetweet(data['id']) + with self.api.lock: + self.api.PostRetweet(data['id']) def on_reply_to(self, widget, data): @@ -262,7 +264,7 @@ class MyTwitter(): self.remove_view(name, single_tweet) - def add_to_notebook(self, name, single_tweet=None): + def add_to_notebook(self, name, single_tweet=None, update=True): # If it already exists, don't add it, just switch to it for i in range(self.tweet_notebook.get_n_pages()): pane = self.tweet_notebook.get_nth_page(i) @@ -308,8 +310,9 @@ class MyTwitter(): pane=new_pane, single_tweet=single_tweet).start() - self.update_windows() - self.tweet_notebook.set_current_page(-1) # switch to the new pane + if update: + self.update_windows() + self.tweet_notebook.set_current_page(-1) # switch to the new pane def on_tab_change(self, event, page, page_num): @@ -356,10 +359,12 @@ class MyTwitter(): current_pane = self.tweet_notebook.get_nth_page(self.tweet_notebook.get_current_page()) user_name = re.sub('^user: ', '', current_pane.get_list_name()) if current_pane.get_following(): - self.api.DestroyFriendship(user_name) + with self.api.lock: + self.api.DestroyFriendship(user_name) current_pane.set_following(False) else: - self.api.CreateFriendship(user_name) + with self.api.lock: + self.api.CreateFriendship(user_name) current_pane.set_following(True) self.update_follow_button(current_pane)