Merge lp:~jsjgruber/ubuntu/precise/lernid/ubuntu-proposed into lp:ubuntu/precise/lernid

Proposed by John S. Gruber
Status: Merged
Merged at revision: 11
Proposed branch: lp:~jsjgruber/ubuntu/precise/lernid/ubuntu-proposed
Merge into: lp:ubuntu/precise/lernid
Diff against target: 359 lines (+146/-29)
6 files modified
debian/changelog (+18/-0)
lernid/Event.py (+4/-1)
lernid/Sessions.py (+39/-11)
lernid/widgets/Browser.py (+1/-1)
lernid/widgets/Schedule.py (+66/-11)
lernid/widgets/Slide.py (+18/-5)
To merge this branch: bzr merge lp:~jsjgruber/ubuntu/precise/lernid/ubuntu-proposed
Reviewer Review Type Date Requested Status
Daniel Holbach (community) Approve
John S. Gruber (community) Needs Resubmitting
Brian Murray Needs Fixing
Ubuntu branches Pending
Review via email: mp+101766@code.launchpad.net

Description of the change

Patches to fix the two related bugs. Tested 11 Apr 12 in #ubuntu-classroom with a member of the classroom team.

I'm afraid I mastakenly burned through 0.8.2.2ubuntu1 in testing, please use 0.8.2.2ubuntu2 as the release version.

Built package at ppa:jsjgruber/test-lernid-ppa, with its buildlog at https://launchpadlibrarian.net/100043879/buildlog_ubuntu-precise-i386.lernid_0.8.2.2ubuntu2~testlernidppa2_BUILDING.txt.gz

Log of testing in #ubuntu-classroom at http://irclogs.ubuntu.com/2012/04/11/%23ubuntu-classroom.html#t22:59

Changes needed because of two API changes, one in telepathy-idle and one in poppler.

lernid is not in Debian or other non-Ubuntu distribution.

To post a comment you must log in.
Revision history for this message
John S. Gruber (jsjgruber) wrote :

To test, bring up lernid from a terminal with the command:

lernid -v
and see that, starting in a couple of minutes, you get the messages:
DEBUG:root:Started ical load
DEBUG:root:Finished ical load
about every minute.

Revision history for this message
John S. Gruber (jsjgruber) wrote :

Patches are from upstream development and are to be included in our next upstream release.

Revision history for this message
Brian Murray (brian-murray) wrote :

The development of Precise has now reached Final Freeze so we are working all updates as Stable Release Updates. Subsequently, the bugs that this branch fixes will need test cases so we can recreate the problem and ensure that the fix does work. Could you please update the bugs appropriately? Thanks!

review: Needs Fixing
Revision history for this message
John S. Gruber (jsjgruber) wrote :

The bugs have been update with test cases.

Package is in universe.

See also http://irclogs.ubuntu.com/2012/04/11/%23ubuntu-classroom.html#t22:59 for the test of the merge candidate in #ubuntu-classroom

Thank you.

review: Needs Resubmitting
Revision history for this message
Daniel Holbach (dholbach) wrote :

Good work. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2011-12-31 02:04:45 +0000
3+++ debian/changelog 2012-04-12 16:09:22 +0000
4@@ -1,3 +1,21 @@
5+lernid (0.8.2.2ubuntu2) UNRELEASED; urgency=low
6+
7+ * Prepare for application to Ubuntu (universe)
8+
9+ -- John S Gruber <JohnSGruber@gmail.com> Thu, 12 Apr 2012 11:38:30 -0400
10+
11+lernid (0.8.2.2ubuntu2~testlernidppa2) precise; urgency=low
12+
13+ * Dynamically load the schedule each minute at 5 seconds past the minute
14+ Addresses LP: #925756
15+
16+ * Slide.py have poppler render to a cairo surface and use a trick to
17+ get that into the pixmap. (LP: #972069)
18+ (Technique from diogodivision's "BlingSwitcher" through
19+ http://www.mikedesjardins.net. Thank you.)
20+
21+ -- John S Gruber <JohnSGruber@gmail.com> Wed, 04 Apr 2012 22:05:00 -0400
22+
23 lernid (0.8.2.2~lr2build1) precise; urgency=low
24
25 * Rebuild to drop python2.6 dependencies.
26
27=== modified file 'lernid/Event.py'
28--- lernid/Event.py 2011-09-03 23:25:12 +0000
29+++ lernid/Event.py 2012-04-12 16:09:22 +0000
30@@ -41,7 +41,10 @@
31
32 @property
33 def icalurl(self):
34- return self._icalurl
35+ if self._icalurl.upper().startswith("HTTPS"):
36+ return self._icalurl[0:4] + self._icalurl[5:]
37+ else:
38+ return self._icalurl
39
40 @property
41 def classroom(self):
42
43=== modified file 'lernid/Sessions.py'
44--- lernid/Sessions.py 2011-09-03 23:25:12 +0000
45+++ lernid/Sessions.py 2012-04-12 16:09:22 +0000
46@@ -25,6 +25,7 @@
47
48 import lernid.DateTime as dt
49 import io
50+import gio, glib
51 from datetime import timedelta
52
53 one_week = timedelta(days=7)
54@@ -35,8 +36,15 @@
55
56 def __init__(self, **kwargs):
57 for k, v in kwargs.iteritems():
58- if k in ('title', 'description', 'instructors', 'helpers', 'local_start', 'local_end', 'slides', 'event', 'locale', 'question_token'):
59- setattr(self, '_'+k, v)
60+ if k in ('uid', 'title', 'description', 'instructors',
61+ 'helpers', 'local_start', 'local_end', 'slides',
62+ 'event', 'locale',
63+ 'question_token'):
64+ setattr(self, '_'+k, v)
65+
66+ @property
67+ def uid(self):
68+ return self._uid
69
70 @property
71 def title(self):
72@@ -103,9 +111,12 @@
73 if now > end:
74 return self.PAST
75
76+def start_read_ical(url, when_done):
77+ gfile = gio.File(uri=url)
78+ r = gfile.load_contents_async(when_done)
79
80-def parse_ical(event):
81- """Parse iCal schedule for event and generate a list of Session objects"""
82+def read_ical(gfile,result,userdata):
83+ event, old_ical = userdata
84 default_cal_error = (
85 u"""BEGIN:VCALENDAR
86 BEGIN:VEVENT
87@@ -115,7 +126,23 @@
88 SUMMARY: """ + _('Unable to load calendar %s\n') +
89 u"""END:VEVENT
90 END:VCALENDAR""") % event.icalurl
91- default_cal_error2 = (
92+
93+ try:
94+
95+ calstring = gfile.load_contents_finish(result)[0]
96+ ical = io.StringIO(unicode(calstring))
97+ except (gio.Error, glib.GError):
98+ logging.error('Unable to open calendar %s' % event.icalurl)
99+ if old_ical:
100+ ical = old_ical
101+ ical.seek(0)
102+ else:
103+ ical = io.StringIO(default_cal_error)
104+ return ical
105+
106+
107+def parse_ical(event, ical):
108+ default_cal_error = (
109 u"""BEGIN:VCALENDAR
110 BEGIN:VEVENT
111 DTSTART:20100101T000000Z
112@@ -124,16 +151,12 @@
113 SUMMARY: """ + _('Unable to parse calendar %s\n') +
114 u"""END:VEVENT
115 END:VCALENDAR""") % event.icalurl
116- try:
117- ical = urllib2.urlopen(event.icalurl, None, 30)
118- except IOError:
119- logging.error('Unable to open calendar %s' % event.icalurl)
120- ical = io.StringIO(default_cal_error)
121+
122 try:
123 cal = vobject.readOne(ical)
124 except:
125 logging.critical('Error parsing calendar at %s' % event.icalurl)
126- cal = vobject.readOne(io.StringIO(default_cal_error2))
127+ cal = vobject.readOne(io.StringIO(default_cal_error))
128
129 sessions = []
130
131@@ -158,12 +181,17 @@
132 summary = session.summary.value
133 else:
134 summary = _('Missing Session Name')
135+ if hasattr(session, "uid"):
136+ uid = session.uid
137+ else:
138+ uid = 0
139 if eventstart_local <= local_start <= eventend_local:
140 if local_start > dt.now_local() - one_week:
141 sessions.append(Session(
142 title = summary,
143 local_start = local_start,
144 local_end = local_end,
145+ uid = uid,
146 **session_data))
147
148 # reverse the list to get the events in chronological order
149
150=== modified file 'lernid/widgets/Browser.py'
151--- lernid/widgets/Browser.py 2011-09-03 23:25:12 +0000
152+++ lernid/widgets/Browser.py 2012-04-12 16:09:22 +0000
153@@ -205,7 +205,7 @@
154 self.set_location(url)
155
156 def _classroom_msg_received(self, classroom, chan, sender, text):
157- if not Options.get('unsafe-override') and not chan.is_moderated() and not self._on_faculty(sender):
158+ if not Options.get('unsafe-override') and not self._on_faculty(sender):
159 return
160 load, ignore = self._parse_urls(text)
161 if load and not self._paused:
162
163=== modified file 'lernid/widgets/Schedule.py'
164--- lernid/widgets/Schedule.py 2011-09-03 23:25:12 +0000
165+++ lernid/widgets/Schedule.py 2012-04-12 16:09:22 +0000
166@@ -23,6 +23,8 @@
167 import gtk
168 import os
169 import time
170+import urlparse
171+import random
172 import pynotify
173 import logging
174 from datetime import timedelta
175@@ -30,7 +32,7 @@
176 import lernid.DateTime as dt
177 from lernid.widgets.Widget import Widget
178 from lernid.lernidconfig import get_data_path
179-from lernid.Sessions import Session, parse_ical
180+from lernid.Sessions import Session, parse_ical, read_ical, start_read_ical
181
182
183 class Schedule(Widget):
184@@ -116,11 +118,68 @@
185 self._schedule = None
186 self._current_session = None
187 self._update_handle = None
188+ self._ical = None
189+ self._icalurl = None
190+ self._connected = False
191
192 self.show_all()
193
194 def do_event_connect(self, event_man, event):
195 self._event = event_man.current_event()
196+ self._connected = True
197+ def finished_initial_ical_load(gfile, result):
198+ self._ical = read_ical(gfile, result, (event,self._ical))
199+ logging.debug('Finished initial ical load')
200+ if not (gfile.get_uri() == self._icalurl):
201+ logging.debug('Old I/O completion ignored')
202+ return
203+ self._update(event, self._ical)
204+ secs = time.localtime().tm_sec
205+ self._update_handle = glib.timeout_add_seconds(65-secs, set_timeout)
206+ def scramble_case(string):
207+ random.seed()
208+ newstring = ''
209+ for s in string:
210+ if random.random() >= .5:
211+ newstring += s.upper()
212+ else:
213+ newstring += s.lower()
214+ return newstring
215+ # scramble the case of the url for this event connection.
216+ # Avoids occasional problem with gvfsd-http wedging
217+ # particular url's after loss of connectivity
218+ parsed = [j for j in urlparse.urlparse(event.icalurl)]
219+ parsed[1]= scramble_case(parsed[1])
220+ self._icalurl = urlparse.urlunparse(parsed)
221+ start_read_ical(self._icalurl, finished_initial_ical_load)
222+ logging.debug('Started initial ical load %s' % self._icalurl)
223+
224+ def set_timeout():
225+ if self._connected:
226+ self._update_handle = glib.timeout_add_seconds(60, self._calendar_refresh, event, self._ical)
227+ # Wait at least one minute before updating again,
228+ # to avoid duplicate notifications.
229+ logging.debug("In set_timeout")
230+ return False
231+
232+ def _calendar_refresh(self, event, ical):
233+ def finished_refresh_ical_load(gfile, result):
234+ self._ical = read_ical(gfile, result, (event, self._ical))
235+ if not (gfile.get_uri() == self._icalurl):
236+ logging.debug('Old I/O completion ignored')
237+ return
238+ logging.debug('Finished ical load')
239+ if not self._connected:
240+ return
241+ self._update(event, self._ical)
242+ secs = time.localtime().tm_sec
243+ self._update_handle = glib.timeout_add_seconds(65-secs, self._calendar_refresh, event, self._ical)
244+ return
245+ start_read_ical(self._icalurl, finished_refresh_ical_load)
246+ logging.debug('Started ical load')
247+ return False
248+
249+ def _update(self, event, ical):
250 def quote(text):
251 quote_table = {
252 '<' : '&lt;',
253@@ -134,7 +193,7 @@
254 return_value= return_value + quote_table.get(l,l)
255 return return_value
256
257- self._schedule = parse_ical(event)
258+ self._schedule = parse_ical(event, ical)
259 self._model.clear()
260 self._scroll_to = None
261 for session in self._schedule:
262@@ -153,22 +212,16 @@
263 current_row = self._model.append(sessionrow)
264 if not self._scroll_to and session.state in (session.FUTURE, session.NOW):
265 self._scroll_to = self._model.get_path(current_row)
266-
267- self._update()
268+ self._update_currency()
269
270 if self._scroll_to:
271 self._treeview.scroll_to_cell(self._scroll_to, use_align=True, row_align=.2)
272
273- def set_timeout():
274- self._update_handle = glib.timeout_add_seconds(60, self._update)
275- # Wait at least one minute before updating again,
276- # to avoid duplicate notifications.
277- secs = time.localtime().tm_sec
278- glib.timeout_add_seconds(65-secs, set_timeout)
279
280 self._treeview.set_headers_visible(True)
281
282 def do_event_disconnect(self, event_man, event):
283+ self._connected = False
284 self._schedule = []
285 self._current_session = None
286 self._model.clear()
287@@ -176,13 +229,15 @@
288 glib.source_remove(self._update_handle)
289 self._treeview.set_headers_visible(False)
290
291- def _update(self):
292+ def _update_currency(self):
293 ended = False
294 if self._current_session and self._current_session.state == Session.PAST:
295 ended = True
296 self._current_session = None
297 for i, row in enumerate(self._model):
298 session = row[self.COL_SESSION]
299+ if self._current_session and self._current_session.uid == session.uid:
300+ self._current_session = session
301 if session.state == Session.NOW:
302 row[self.COL_ICON] = gtk.STOCK_GO_FORWARD
303 if session != self._current_session:
304
305=== modified file 'lernid/widgets/Slide.py'
306--- lernid/widgets/Slide.py 2011-09-03 23:25:12 +0000
307+++ lernid/widgets/Slide.py 2012-04-12 16:09:22 +0000
308@@ -26,6 +26,8 @@
309 import logging
310 import gio
311 import webbrowser
312+import cairo
313+import StringIO
314
315 from lernid.widgets.Widget import Widget
316 from lernid.lernidconfig import save_cache_path
317@@ -113,17 +115,17 @@
318 def _classroom_msg_received(self, classroom, chan, sender, text):
319 matches = re.search(r'\[(?i)SLIDE\s+(\d+).*\]', text)
320 if matches and self._session_slide_downloaded:
321- if chan.is_moderated() or self._on_faculty(sender):
322+ if self._on_faculty(sender):
323 self._change_slide_page(int(matches.groups()[0]))
324 return
325 matches = re.search(r'\[(?i)SLIDEFILE\s+(\S+)\s+(\d+).*\]', text)
326 if matches:
327- if chan.is_moderated() or self._on_faculty(sender):
328+ if self._on_faculty(sender):
329 self._download_slides(matches.groups()[0], int(matches.groups()[1]))
330 return
331 matches = re.search(r'\[(?i)SLIDEFILE\s+(\S+).*\]', text)
332 if matches:
333- if chan.is_moderated() or self._on_faculty(sender):
334+ if self._on_faculty(sender):
335 self._download_slides(matches.groups()[0], 1)
336 return
337
338@@ -137,8 +139,19 @@
339 return
340 page = pdf.get_page(int(pagenumber) - 1)
341 w, h = page.get_size()
342- pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, int(w), int(h))
343- page.render_to_pixbuf(0,0,int(w),int(h),1,0, pixbuf)
344+ w, h = (int(w), int(h))
345+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
346+ context = cairo.Context(surface)
347+ page.render(context)
348+ # technique from diogodivision's "BlingSwitcher",
349+ # through http://www.mikedesjardins.net
350+ temp = StringIO.StringIO()
351+ surface.write_to_png(temp)
352+ temp.seek(0)
353+ loader = gtk.gdk.PixbufLoader()
354+ loader.write(temp.getvalue())
355+ loader.close()
356+ pixbuf = loader.get_pixbuf()
357 except:
358 Statusbar.push_message(_('An error was encountered while trying to load slide number {0}'.format(pagenumber)), duration=120)
359 logging.debug("Something went wrong when loading slide %s" % pagenumber)

Subscribers

People subscribed via source and target branches