Repository: pimutils/khal
Pull request #605 (2017-03-19 19:58:08)

Remove leftovers from python2-compatibility


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a58b66a..bf6e604 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,7 +14,8 @@ may want to subscribe to `GitHub's tag feed
 
 * NEW ikhal's event editor now allows better editing of recurrence rules,
   including INTERVALs, end dates, and more
-
+* NEW ikhal will now check if any configured vdir has been updated, and, if
+  applicable, refresh its UI to reflect the latest changes 
 
 0.9.3
 =====
diff --git a/khal/khalendar/backend.py b/khal/khalendar/backend.py
index df3e075..ed845f4 100644
--- a/khal/khalendar/backend.py
+++ b/khal/khalendar/backend.py
@@ -160,7 +160,7 @@ class SQLiteDb(object):
         """
         self.cursor.execute('CREATE TABLE IF NOT EXISTS '
                             'version (version INTEGER)')
-        logger.debug(u"created version table")
+        logger.debug("created version table")
 
         self.cursor.execute('''CREATE TABLE IF NOT EXISTS calendars (
             calendar TEXT NOT NULL UNIQUE,
@@ -207,7 +207,7 @@ class SQLiteDb(object):
             result = self.cursor.fetchone()
 
             if result[0] != 0:
-                logger.debug(u"tables for calendar {0} exist".format(cal))
+                logger.debug("tables for calendar {0} exist".format(cal))
             else:
                 sql_s = 'INSERT INTO calendars (calendar, resource) VALUES (?, ?);'
                 stuple = (cal, '')
diff --git a/khal/khalendar/khalendar.py b/khal/khalendar/khalendar.py
index 527a182..62598dc 100644
--- a/khal/khalendar/khalendar.py
+++ b/khal/khalendar/khalendar.py
@@ -96,6 +96,7 @@ class CalendarCollection(object):
         self._locale = locale
         self._backend = backend.SQLiteDb(
             calendars=self.names, db_path=dbpath, locale=self._locale)
+        self._last_ctags = dict()
         self.update_db()
 
     @property
@@ -236,15 +237,45 @@ class CalendarCollection(object):
         should be called after every change to the vdir
         """
         for calendar in self._calendars:
-            if self._needs_update(calendar):
+            if self._needs_update(calendar, remember=True):
                 self._db_update(calendar)
 
-    def _needs_update(self, calendar):
+    def needs_update(self):
+        """Check if you need to call update_db.
+
+        This could either be the case because the vdirs were changed externally,
+        or another instance of khal updated the caching db already.
+        """
+        # TODO is it a good idea to munch both use cases together?
+        # in case another instance of khal has updated the db, we only need
+        # to get new events, but # update_db() takes potentially a long time to return
+        # but then the code (in ikhal's refresh code) would need to look like
+        # this:
+        #
+        # update_ui = False
+        # if collection.needs_update():
+        #   collection.update_db()
+        #   update_ui = True
+        # if collection.needs_refresh() or update_ui:
+        #   do_the_update()
+        #
+        # and the API would be made even uglier than it already is...
+        for calendar in self._calendars:
+            if self._needs_update(calendar) or \
+                    self._last_ctags[calendar] != self._local_ctag(calendar):
+                return True
+        return False
+
+    def _needs_update(self, calendar, remember=False):
         """checks if the db for the given calendar needs an update"""
-        return self._local_ctag(calendar) != self._backend.get_ctag(calendar)
+        local_ctag = self._local_ctag(calendar)
+        if remember:
+            self._last_ctags[calendar] = local_ctag
+        return local_ctag != self._backend.get_ctag(calendar)
 
     def _db_update(self, calendar):
         """implements the actual db update on a per calendar base"""
+        local_ctag = self._local_ctag(calendar)
         db_hrefs = set(href for href, etag in self._backend.list(calendar))
         storage_hrefs = set()
 
@@ -257,7 +288,8 @@ class CalendarCollection(object):
                     self._update_vevent(href, calendar=calendar)
             for href in db_hrefs - storage_hrefs:
                 self._backend.delete(href, calendar=calendar)
-            self._backend.set_ctag(self._local_ctag(calendar), calendar=calendar)
+            self._backend.set_ctag(local_ctag, calendar=calendar)
+            self._last_ctags[calendar] = local_ctag
 
     def _update_vevent(self, href, calendar):
         """should only be called during db_update, only updates the db,
diff --git a/khal/khalendar/vdir.py b/khal/khalendar/vdir.py
index b0c4f72..591ab8d 100644
--- a/khal/khalendar/vdir.py
+++ b/khal/khalendar/vdir.py
@@ -121,7 +121,7 @@ class Item:
 
     @cached_property
     def uid(self):
-        uid = u''
+        uid = ''
         lines = iter(self.raw.splitlines())
         for line in lines:
             if line.startswith('UID:'):
@@ -129,7 +129,7 @@ class Item:
                 break
 
         for line in lines:
-            if not line.startswith(u' '):
+            if not line.startswith(' '):
                 break
             uid += line[1:]
 
@@ -137,7 +137,7 @@ class Item:
 
 
 def _normalize_meta_value(value):
-    return to_unicode(value or u'').strip()
+    return to_unicode(value or '').strip()
 
 
 class VdirBase:
@@ -273,7 +273,7 @@ class VdirBase:
                 raise
 
     def set_meta(self, key, value):
-        value = value or u''
+        value = value or ''
         assert isinstance(value, str)
         fpath = os.path.join(self.path, key)
         with atomic_write(fpath, mode='wb', overwrite=True) as f:
diff --git a/khal/log.py b/khal/log.py
index 6afdb26..dda5cd8 100644
--- a/khal/log.py
+++ b/khal/log.py
@@ -48,7 +48,7 @@ class ColorFormatter(logging.Formatter):
         return logging.Formatter.format(self, record)
 
 
-class ClickStream(object):
+class ClickStream:
 
     def write(self, string):
         click.echo(string, file=sys.stderr, nl=False)
diff --git a/khal/ui/__init__.py b/khal/ui/__init__.py
index 1386e3d..1b22a8a 100644
--- a/khal/ui/__init__.py
+++ b/khal/ui/__init__.py
@@ -691,7 +691,7 @@ class EventColumn(urwid.WidgetWrap):
         """
         if event.readonly:
             self.pane.window.alert(
-                ('light red', 'Calendar {} is read-only.'.format(event.calendar)))
+                ('alert', 'Calendar `{}` is read-only.'.format(event.calendar)))
             return
 
         if isinstance(event.start_local, datetime):
@@ -769,12 +769,10 @@ class EventColumn(urwid.WidgetWrap):
                 self.focus_event.event.export_ics(user_data.get_edit_text())
             except Exception as error:
                 self.pane.window.backtrack()
-                self.pane.window.alert(
-                    ('light red', 'Failed to save event: %s' % error))
+                self.pane.window.alert(('alert', 'Failed to save event: %s' % error))
             else:
                 self.pane.window.backtrack()
-                self.pane.window.alert(
-                    ('light green', 'Event successfully exported'))
+                self.pane.window.alert('Event successfully exported')
 
         overlay = urwid.Overlay(
             ExportDialog(
@@ -803,9 +801,9 @@ class EventColumn(urwid.WidgetWrap):
                 event.event.start_local, event.event.end_local, event.event.recurring)
 
         if event.event.readonly:
-            self.eventcolumn.pane.window.alert(
-                ('light red',
-                 'Calendar {} is read-only.'.format(self.event.calendar)))
+            self.pane.window.alert(
+                ('alert', 'Calendar {} is read-only.'.format(event.event.calendar)),
+            )
             return
         status = self.delete_status(event.recuid)
         refresh = True
@@ -865,7 +863,7 @@ class EventColumn(urwid.WidgetWrap):
         :type end: datetime.date
         """
         if not self.pane.collection.writable_names:
-            self.pane.window.alert(('light red', 'No writable calendar.'))
+            self.pane.window.alert(('alert', 'No writable calendar.'))
             return
         if end is None:
             start = datetime.combine(date, time(datetime.now().hour))
@@ -912,7 +910,7 @@ class EventColumn(urwid.WidgetWrap):
                     # the event in focus is already viewed -> edit
                     self.clear_event_view()  # do not move before the if condition
                     if self.delete_status(self.focus_event.recuid):
-                        self.pane.window.alert(('light red', 'This event is marked as deleted'))
+                        self.pane.window.alert(('alert', 'This event is marked as deleted'))
                     self.edit(self.focus_event.event)
                 else:
                     self.clear_event_view()
@@ -1267,8 +1265,6 @@ def start_pane(pane, callback, program_info='', quit_keys=['q']):
     frame.loop = loop
 
     def redraw_today(loop, pane, meta={'last_today': None}):
-        # TODO calculate how many seconds the new day is away and set timer
-        # to that
         # XXX TODO this currently assumes, today moves forward by exactly one
         # day, but it could either move forward more (suspend-to-disk/ram) or
         # even move backwards
@@ -1279,7 +1275,17 @@ def start_pane(pane, callback, program_info='', quit_keys=['q']):
             pane.eventscolumn.original_widget.update_date_line()
         loop.set_alarm_in(60, redraw_today, pane)
 
-    redraw_today(frame.loop, pane)
+    loop.set_alarm_in(60, redraw_today, pane)
+
+    def check_for_updates(loop, pane):
+        if pane.collection.needs_update():
+            pane.window.alert('detected external vdir modification, updating...')
+            pane.collection.update_db()
+            pane.eventscolumn.base_widget.update(None, None, everything=True)
+            pane.window.alert('detected external vdir modification, updated.')
+        loop.set_alarm_in(60, check_for_updates, pane)
+
+    loop.set_alarm_in(60, check_for_updates, pane)
     # Make urwid use 256 color mode.
     loop.screen.set_terminal_properties(
         colors=256, bright_is_bold=pane._conf['view']['bold_for_light_color'])
diff --git a/khal/ui/base.py b/khal/ui/base.py
index d8cbde1..4ab6edc 100644
--- a/khal/ui/base.py
+++ b/khal/ui/base.py
@@ -111,9 +111,9 @@ class Window(urwid.Frame):
 
         header = urwid.AttrWrap(urwid.Text(''), 'header')
         footer = urwid.AttrWrap(urwid.Text(footer), 'footer')
-        urwid.Frame.__init__(self, urwid.Text(''),
-                             header=header,
-                             footer=footer)
+        urwid.Frame.__init__(
+            self, urwid.Text(''), header=header, footer=footer,
+        )
         self.update_header()
         self._original_w = None
         self.quit_keys = quit_keys
@@ -172,7 +172,13 @@ class Window(urwid.Frame):
         return self._track[-1][0] if self._track else None
 
     def update_header(self, alert=None):
-        # TODO setting colors does not work, but is actually used
+        """Update the Windows header line.
+
+        :param alert: additional text to show in header, additionally to
+            the current title. If `alert` is a tuple, the first entry must
+            be a valid palette entry
+        :type alert: str or (palette_entry, str)
+        """
         pane_title = getattr(self._get_current_pane(), 'title', None)
         text = []
 


Test patch:
diff --git a/tests/controller_test.py b/tests/controller_test.py
index b37c627..a90e781 100644
--- a/tests/controller_test.py
+++ b/tests/controller_test.py
@@ -16,7 +16,7 @@ today = dt.date.today()
 yesterday = today - dt.timedelta(days=1)
 tomorrow = today + dt.timedelta(days=1)
 
-event_allday_template = u"""BEGIN:VEVENT
+event_allday_template = """BEGIN:VEVENT
 SEQUENCE:0
 UID:uid3@host1.com
 DTSTART;VALUE=DATE:{}
@@ -38,7 +38,7 @@ conf = {'locale': utils.LOCALE_BERLIN,
         }
 
 
-class TestGetAgenda(object):
+class TestGetAgenda:
     def test_new_event(self, coll_vdirs):
         coll, vdirs = coll_vdirs
         event = coll.new_event(event_today, utils.cal1)
@@ -93,7 +93,7 @@ class TestGetAgenda(object):
             khal_list(coll, [], conf, agenda_format=event_format, day_format="{name}")).lower()
 
 
-class TestImport(object):
+class TestImport:
     def test_import(self, coll_vdirs):
         coll, vdirs = coll_vdirs
         view = {'event_format': '{title}'}
diff --git a/tests/khalendar_test.py b/tests/khalendar_test.py
index 76c6988..2387cd0 100644
--- a/tests/khalendar_test.py
+++ b/tests/khalendar_test.py
@@ -21,7 +21,7 @@ today = date.today()
 yesterday = today - timedelta(days=1)
 tomorrow = today + timedelta(days=1)
 
-event_allday_template = u"""BEGIN:VEVENT
+event_allday_template = """BEGIN:VEVENT
 SEQUENCE:0
 UID:uid3@host1.com
 DTSTART;VALUE=DATE:{}
diff --git a/tests/terminal_test.py b/tests/terminal_test.py
index c5aca23..ccc84c8 100644
--- a/tests/terminal_test.py
+++ b/tests/terminal_test.py
@@ -14,7 +14,7 @@ def test_colored():
         '\x1b[38;2;255;0;255m\x1b[48;2;0;170;187mtäst\x1b[0m'
 
 
-class TestMergeColumns(object):
+class TestMergeColumns:
 
     def test_longer_right(self):
         left = ['uiae', 'nrtd']
diff --git a/tests/utils_test.py b/tests/utils_test.py
index 711277a..974cf35 100644
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -89,7 +89,7 @@ def test_normalize_component():
     """))
 
 
-class TestGuessDatetimefstr(object):
+class TestGuessDatetimefstr:
     tomorrow16 = datetime.combine(tomorrow, time(16, 0))
 
     def test_today(self):
@@ -139,7 +139,7 @@ class TestGuessDatetimefstr(object):
             )
 
 
-class TestGuessTimedeltafstr(object):
+class TestGuessTimedeltafstr:
 
     def test_single(self):
         assert timedelta(minutes=10) == guesstimedeltafstr('10m')
@@ -171,7 +171,7 @@ class TestGuessTimedeltafstr(object):
             guesstimedeltafstr('10min 10minutes')
 
 
-class TestGuessRangefstr(object):
+class TestGuessRangefstr:
     td_1d = timedelta(days=1)
     today_start = datetime.combine(date.today(), time.min)
     tomorrow_start = today_start + td_1d
@@ -277,7 +277,7 @@ class TestGuessRangefstr(object):
                 guessrangefstr('2017-1-1 2017-1-1', locale=locale)
 
 
-class TestTimeDelta2Str(object):
+class TestTimeDelta2Str:
 
     def test_single(self):
         assert timedelta2str(timedelta(minutes=10)) == '10m'