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
=== modified file 'debian/changelog'
--- debian/changelog 2011-12-31 02:04:45 +0000
+++ debian/changelog 2012-04-12 16:09:22 +0000
@@ -1,3 +1,21 @@
1lernid (0.8.2.2ubuntu2) UNRELEASED; urgency=low
2
3 * Prepare for application to Ubuntu (universe)
4
5 -- John S Gruber <JohnSGruber@gmail.com> Thu, 12 Apr 2012 11:38:30 -0400
6
7lernid (0.8.2.2ubuntu2~testlernidppa2) precise; urgency=low
8
9 * Dynamically load the schedule each minute at 5 seconds past the minute
10 Addresses LP: #925756
11
12 * Slide.py have poppler render to a cairo surface and use a trick to
13 get that into the pixmap. (LP: #972069)
14 (Technique from diogodivision's "BlingSwitcher" through
15 http://www.mikedesjardins.net. Thank you.)
16
17 -- John S Gruber <JohnSGruber@gmail.com> Wed, 04 Apr 2012 22:05:00 -0400
18
1lernid (0.8.2.2~lr2build1) precise; urgency=low19lernid (0.8.2.2~lr2build1) precise; urgency=low
220
3 * Rebuild to drop python2.6 dependencies.21 * Rebuild to drop python2.6 dependencies.
422
=== modified file 'lernid/Event.py'
--- lernid/Event.py 2011-09-03 23:25:12 +0000
+++ lernid/Event.py 2012-04-12 16:09:22 +0000
@@ -41,7 +41,10 @@
4141
42 @property42 @property
43 def icalurl(self):43 def icalurl(self):
44 return self._icalurl44 if self._icalurl.upper().startswith("HTTPS"):
45 return self._icalurl[0:4] + self._icalurl[5:]
46 else:
47 return self._icalurl
4548
46 @property49 @property
47 def classroom(self):50 def classroom(self):
4851
=== modified file 'lernid/Sessions.py'
--- lernid/Sessions.py 2011-09-03 23:25:12 +0000
+++ lernid/Sessions.py 2012-04-12 16:09:22 +0000
@@ -25,6 +25,7 @@
2525
26import lernid.DateTime as dt26import lernid.DateTime as dt
27import io27import io
28import gio, glib
28from datetime import timedelta29from datetime import timedelta
2930
30one_week = timedelta(days=7)31one_week = timedelta(days=7)
@@ -35,8 +36,15 @@
3536
36 def __init__(self, **kwargs):37 def __init__(self, **kwargs):
37 for k, v in kwargs.iteritems():38 for k, v in kwargs.iteritems():
38 if k in ('title', 'description', 'instructors', 'helpers', 'local_start', 'local_end', 'slides', 'event', 'locale', 'question_token'):39 if k in ('uid', 'title', 'description', 'instructors',
39 setattr(self, '_'+k, v)40 'helpers', 'local_start', 'local_end', 'slides',
41 'event', 'locale',
42 'question_token'):
43 setattr(self, '_'+k, v)
44
45 @property
46 def uid(self):
47 return self._uid
4048
41 @property49 @property
42 def title(self):50 def title(self):
@@ -103,9 +111,12 @@
103 if now > end:111 if now > end:
104 return self.PAST112 return self.PAST
105113
114def start_read_ical(url, when_done):
115 gfile = gio.File(uri=url)
116 r = gfile.load_contents_async(when_done)
106117
107def parse_ical(event):118def read_ical(gfile,result,userdata):
108 """Parse iCal schedule for event and generate a list of Session objects"""119 event, old_ical = userdata
109 default_cal_error = (120 default_cal_error = (
110u"""BEGIN:VCALENDAR121u"""BEGIN:VCALENDAR
111BEGIN:VEVENT122BEGIN:VEVENT
@@ -115,7 +126,23 @@
115SUMMARY: """ + _('Unable to load calendar %s\n') + 126SUMMARY: """ + _('Unable to load calendar %s\n') +
116u"""END:VEVENT127u"""END:VEVENT
117END:VCALENDAR""") % event.icalurl128END:VCALENDAR""") % event.icalurl
118 default_cal_error2 = (129
130 try:
131
132 calstring = gfile.load_contents_finish(result)[0]
133 ical = io.StringIO(unicode(calstring))
134 except (gio.Error, glib.GError):
135 logging.error('Unable to open calendar %s' % event.icalurl)
136 if old_ical:
137 ical = old_ical
138 ical.seek(0)
139 else:
140 ical = io.StringIO(default_cal_error)
141 return ical
142
143
144def parse_ical(event, ical):
145 default_cal_error = (
119u"""BEGIN:VCALENDAR146u"""BEGIN:VCALENDAR
120BEGIN:VEVENT147BEGIN:VEVENT
121DTSTART:20100101T000000Z148DTSTART:20100101T000000Z
@@ -124,16 +151,12 @@
124SUMMARY: """ + _('Unable to parse calendar %s\n') + 151SUMMARY: """ + _('Unable to parse calendar %s\n') +
125u"""END:VEVENT152u"""END:VEVENT
126END:VCALENDAR""") % event.icalurl153END:VCALENDAR""") % event.icalurl
127 try:154
128 ical = urllib2.urlopen(event.icalurl, None, 30)
129 except IOError:
130 logging.error('Unable to open calendar %s' % event.icalurl)
131 ical = io.StringIO(default_cal_error)
132 try:155 try:
133 cal = vobject.readOne(ical)156 cal = vobject.readOne(ical)
134 except:157 except:
135 logging.critical('Error parsing calendar at %s' % event.icalurl)158 logging.critical('Error parsing calendar at %s' % event.icalurl)
136 cal = vobject.readOne(io.StringIO(default_cal_error2))159 cal = vobject.readOne(io.StringIO(default_cal_error))
137160
138 sessions = []161 sessions = []
139162
@@ -158,12 +181,17 @@
158 summary = session.summary.value181 summary = session.summary.value
159 else:182 else:
160 summary = _('Missing Session Name')183 summary = _('Missing Session Name')
184 if hasattr(session, "uid"):
185 uid = session.uid
186 else:
187 uid = 0
161 if eventstart_local <= local_start <= eventend_local:188 if eventstart_local <= local_start <= eventend_local:
162 if local_start > dt.now_local() - one_week:189 if local_start > dt.now_local() - one_week:
163 sessions.append(Session(190 sessions.append(Session(
164 title = summary,191 title = summary,
165 local_start = local_start,192 local_start = local_start,
166 local_end = local_end,193 local_end = local_end,
194 uid = uid,
167 **session_data))195 **session_data))
168196
169 # reverse the list to get the events in chronological order197 # reverse the list to get the events in chronological order
170198
=== modified file 'lernid/widgets/Browser.py'
--- lernid/widgets/Browser.py 2011-09-03 23:25:12 +0000
+++ lernid/widgets/Browser.py 2012-04-12 16:09:22 +0000
@@ -205,7 +205,7 @@
205 self.set_location(url)205 self.set_location(url)
206206
207 def _classroom_msg_received(self, classroom, chan, sender, text):207 def _classroom_msg_received(self, classroom, chan, sender, text):
208 if not Options.get('unsafe-override') and not chan.is_moderated() and not self._on_faculty(sender):208 if not Options.get('unsafe-override') and not self._on_faculty(sender):
209 return209 return
210 load, ignore = self._parse_urls(text)210 load, ignore = self._parse_urls(text)
211 if load and not self._paused:211 if load and not self._paused:
212212
=== modified file 'lernid/widgets/Schedule.py'
--- lernid/widgets/Schedule.py 2011-09-03 23:25:12 +0000
+++ lernid/widgets/Schedule.py 2012-04-12 16:09:22 +0000
@@ -23,6 +23,8 @@
23import gtk23import gtk
24import os24import os
25import time25import time
26import urlparse
27import random
26import pynotify28import pynotify
27import logging29import logging
28from datetime import timedelta30from datetime import timedelta
@@ -30,7 +32,7 @@
30import lernid.DateTime as dt32import lernid.DateTime as dt
31from lernid.widgets.Widget import Widget33from lernid.widgets.Widget import Widget
32from lernid.lernidconfig import get_data_path34from lernid.lernidconfig import get_data_path
33from lernid.Sessions import Session, parse_ical35from lernid.Sessions import Session, parse_ical, read_ical, start_read_ical
3436
3537
36class Schedule(Widget):38class Schedule(Widget):
@@ -116,11 +118,68 @@
116 self._schedule = None118 self._schedule = None
117 self._current_session = None119 self._current_session = None
118 self._update_handle = None120 self._update_handle = None
121 self._ical = None
122 self._icalurl = None
123 self._connected = False
119124
120 self.show_all()125 self.show_all()
121126
122 def do_event_connect(self, event_man, event):127 def do_event_connect(self, event_man, event):
123 self._event = event_man.current_event()128 self._event = event_man.current_event()
129 self._connected = True
130 def finished_initial_ical_load(gfile, result):
131 self._ical = read_ical(gfile, result, (event,self._ical))
132 logging.debug('Finished initial ical load')
133 if not (gfile.get_uri() == self._icalurl):
134 logging.debug('Old I/O completion ignored')
135 return
136 self._update(event, self._ical)
137 secs = time.localtime().tm_sec
138 self._update_handle = glib.timeout_add_seconds(65-secs, set_timeout)
139 def scramble_case(string):
140 random.seed()
141 newstring = ''
142 for s in string:
143 if random.random() >= .5:
144 newstring += s.upper()
145 else:
146 newstring += s.lower()
147 return newstring
148 # scramble the case of the url for this event connection.
149 # Avoids occasional problem with gvfsd-http wedging
150 # particular url's after loss of connectivity
151 parsed = [j for j in urlparse.urlparse(event.icalurl)]
152 parsed[1]= scramble_case(parsed[1])
153 self._icalurl = urlparse.urlunparse(parsed)
154 start_read_ical(self._icalurl, finished_initial_ical_load)
155 logging.debug('Started initial ical load %s' % self._icalurl)
156
157 def set_timeout():
158 if self._connected:
159 self._update_handle = glib.timeout_add_seconds(60, self._calendar_refresh, event, self._ical)
160 # Wait at least one minute before updating again,
161 # to avoid duplicate notifications.
162 logging.debug("In set_timeout")
163 return False
164
165 def _calendar_refresh(self, event, ical):
166 def finished_refresh_ical_load(gfile, result):
167 self._ical = read_ical(gfile, result, (event, self._ical))
168 if not (gfile.get_uri() == self._icalurl):
169 logging.debug('Old I/O completion ignored')
170 return
171 logging.debug('Finished ical load')
172 if not self._connected:
173 return
174 self._update(event, self._ical)
175 secs = time.localtime().tm_sec
176 self._update_handle = glib.timeout_add_seconds(65-secs, self._calendar_refresh, event, self._ical)
177 return
178 start_read_ical(self._icalurl, finished_refresh_ical_load)
179 logging.debug('Started ical load')
180 return False
181
182 def _update(self, event, ical):
124 def quote(text):183 def quote(text):
125 quote_table = {184 quote_table = {
126 '<' : '&lt;',185 '<' : '&lt;',
@@ -134,7 +193,7 @@
134 return_value= return_value + quote_table.get(l,l)193 return_value= return_value + quote_table.get(l,l)
135 return return_value194 return return_value
136195
137 self._schedule = parse_ical(event)196 self._schedule = parse_ical(event, ical)
138 self._model.clear()197 self._model.clear()
139 self._scroll_to = None198 self._scroll_to = None
140 for session in self._schedule:199 for session in self._schedule:
@@ -153,22 +212,16 @@
153 current_row = self._model.append(sessionrow)212 current_row = self._model.append(sessionrow)
154 if not self._scroll_to and session.state in (session.FUTURE, session.NOW):213 if not self._scroll_to and session.state in (session.FUTURE, session.NOW):
155 self._scroll_to = self._model.get_path(current_row)214 self._scroll_to = self._model.get_path(current_row)
156215 self._update_currency()
157 self._update()
158216
159 if self._scroll_to:217 if self._scroll_to:
160 self._treeview.scroll_to_cell(self._scroll_to, use_align=True, row_align=.2)218 self._treeview.scroll_to_cell(self._scroll_to, use_align=True, row_align=.2)
161219
162 def set_timeout():
163 self._update_handle = glib.timeout_add_seconds(60, self._update)
164 # Wait at least one minute before updating again,
165 # to avoid duplicate notifications.
166 secs = time.localtime().tm_sec
167 glib.timeout_add_seconds(65-secs, set_timeout)
168220
169 self._treeview.set_headers_visible(True)221 self._treeview.set_headers_visible(True)
170222
171 def do_event_disconnect(self, event_man, event):223 def do_event_disconnect(self, event_man, event):
224 self._connected = False
172 self._schedule = []225 self._schedule = []
173 self._current_session = None226 self._current_session = None
174 self._model.clear()227 self._model.clear()
@@ -176,13 +229,15 @@
176 glib.source_remove(self._update_handle)229 glib.source_remove(self._update_handle)
177 self._treeview.set_headers_visible(False)230 self._treeview.set_headers_visible(False)
178231
179 def _update(self):232 def _update_currency(self):
180 ended = False233 ended = False
181 if self._current_session and self._current_session.state == Session.PAST:234 if self._current_session and self._current_session.state == Session.PAST:
182 ended = True235 ended = True
183 self._current_session = None236 self._current_session = None
184 for i, row in enumerate(self._model):237 for i, row in enumerate(self._model):
185 session = row[self.COL_SESSION]238 session = row[self.COL_SESSION]
239 if self._current_session and self._current_session.uid == session.uid:
240 self._current_session = session
186 if session.state == Session.NOW:241 if session.state == Session.NOW:
187 row[self.COL_ICON] = gtk.STOCK_GO_FORWARD242 row[self.COL_ICON] = gtk.STOCK_GO_FORWARD
188 if session != self._current_session:243 if session != self._current_session:
189244
=== modified file 'lernid/widgets/Slide.py'
--- lernid/widgets/Slide.py 2011-09-03 23:25:12 +0000
+++ lernid/widgets/Slide.py 2012-04-12 16:09:22 +0000
@@ -26,6 +26,8 @@
26import logging26import logging
27import gio27import gio
28import webbrowser28import webbrowser
29import cairo
30import StringIO
2931
30from lernid.widgets.Widget import Widget32from lernid.widgets.Widget import Widget
31from lernid.lernidconfig import save_cache_path33from lernid.lernidconfig import save_cache_path
@@ -113,17 +115,17 @@
113 def _classroom_msg_received(self, classroom, chan, sender, text):115 def _classroom_msg_received(self, classroom, chan, sender, text):
114 matches = re.search(r'\[(?i)SLIDE\s+(\d+).*\]', text)116 matches = re.search(r'\[(?i)SLIDE\s+(\d+).*\]', text)
115 if matches and self._session_slide_downloaded:117 if matches and self._session_slide_downloaded:
116 if chan.is_moderated() or self._on_faculty(sender):118 if self._on_faculty(sender):
117 self._change_slide_page(int(matches.groups()[0]))119 self._change_slide_page(int(matches.groups()[0]))
118 return120 return
119 matches = re.search(r'\[(?i)SLIDEFILE\s+(\S+)\s+(\d+).*\]', text)121 matches = re.search(r'\[(?i)SLIDEFILE\s+(\S+)\s+(\d+).*\]', text)
120 if matches:122 if matches:
121 if chan.is_moderated() or self._on_faculty(sender):123 if self._on_faculty(sender):
122 self._download_slides(matches.groups()[0], int(matches.groups()[1]))124 self._download_slides(matches.groups()[0], int(matches.groups()[1]))
123 return125 return
124 matches = re.search(r'\[(?i)SLIDEFILE\s+(\S+).*\]', text)126 matches = re.search(r'\[(?i)SLIDEFILE\s+(\S+).*\]', text)
125 if matches:127 if matches:
126 if chan.is_moderated() or self._on_faculty(sender):128 if self._on_faculty(sender):
127 self._download_slides(matches.groups()[0], 1)129 self._download_slides(matches.groups()[0], 1)
128 return130 return
129131
@@ -137,8 +139,19 @@
137 return139 return
138 page = pdf.get_page(int(pagenumber) - 1)140 page = pdf.get_page(int(pagenumber) - 1)
139 w, h = page.get_size()141 w, h = page.get_size()
140 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, int(w), int(h))142 w, h = (int(w), int(h))
141 page.render_to_pixbuf(0,0,int(w),int(h),1,0, pixbuf)143 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
144 context = cairo.Context(surface)
145 page.render(context)
146 # technique from diogodivision's "BlingSwitcher",
147 # through http://www.mikedesjardins.net
148 temp = StringIO.StringIO()
149 surface.write_to_png(temp)
150 temp.seek(0)
151 loader = gtk.gdk.PixbufLoader()
152 loader.write(temp.getvalue())
153 loader.close()
154 pixbuf = loader.get_pixbuf()
142 except:155 except:
143 Statusbar.push_message(_('An error was encountered while trying to load slide number {0}'.format(pagenumber)), duration=120)156 Statusbar.push_message(_('An error was encountered while trying to load slide number {0}'.format(pagenumber)), duration=120)
144 logging.debug("Something went wrong when loading slide %s" % pagenumber)157 logging.debug("Something went wrong when loading slide %s" % pagenumber)

Subscribers

People subscribed via source and target branches