Package Gnumed :: Package timelinelib :: Package dataimport :: Module ics
[frames] | no frames]

Source Code for Module Gnumed.timelinelib.dataimport.ics

  1  # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg 
  2  # 
  3  # This file is part of Timeline. 
  4  # 
  5  # Timeline is free software: you can redistribute it and/or modify 
  6  # it under the terms of the GNU General Public License as published by 
  7  # the Free Software Foundation, either version 3 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # Timeline is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  # GNU General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU General Public License 
 16  # along with Timeline.  If not, see <http://www.gnu.org/licenses/>. 
 17   
 18   
 19  from datetime import date 
 20  from datetime import datetime 
 21  from datetime import timedelta 
 22  from os.path import abspath 
 23  import random 
 24   
 25  from icalendar import Calendar 
 26   
 27  from timelinelib.calendar.gregorian.gregorian import GregorianDateTime 
 28  from timelinelib.calendar.gregorian.time import GregorianDelta 
 29  from timelinelib.canvas.data.db import MemoryDB 
 30  from timelinelib.canvas.data.exceptions import TimelineIOError 
 31  from timelinelib.canvas.data import Category 
 32  from timelinelib.canvas.data import Event 
 33  from timelinelib.utils import ex_msg 
 34  from timelinelib.wxgui.utils import display_warning_message 
 35   
 36   
37 -class IcsLoader(object):
38
39 - def load(self, db, path, options):
40 self.vevents_not_loaded = [] 41 (self.add_location_to_description, 42 self.use_trigger_as_start_date, 43 self.use_trigger_as_alert) = options 44 self.events = [] 45 self.categories = [] 46 self.category_names = [] 47 file_contents = self._read_file_content(path) 48 cal = self._read_calendar_object(file_contents) 49 self._load_events(cal, db) 50 self._load_todos(cal, db) 51 self._save_data_in_db(db) 52 self._report_on_vevents_not_loaded()
53
54 - def _load_events(self, cal, db):
55 for vevent in cal.walk("VEVENT"): 56 self._load_vevent(db, vevent) 57 self._load_categories(vevent)
58
59 - def _load_todos(self, cal, db):
60 for vtodo in cal.walk("VTODO"): 61 self._load_vtodo(db, vtodo) 62 self._load_categories(vtodo)
63
64 - def _load_vtodo(self, db, vtodo):
65 try: 66 start, end = self._extract_todo_start_end(vtodo) 67 txt = self._get_event_name(vtodo) 68 event = Event().update(start, end, txt) 69 event.set_description(self._extract_todo_description(vtodo)) 70 event.set_alert(self._extract_todo_alert(vtodo)) 71 self.events.append(event) 72 except KeyError: 73 pass
74
75 - def _extract_todo_start_end(self, vtodo):
76 end = self._extract_todo_end(vtodo) 77 if self.use_trigger_as_start_date: 78 start = self._extract_todo_start(vtodo) 79 if start is None: 80 start = end 81 else: 82 start = end 83 return start, end
84
85 - def _extract_todo_end(self, vtodo):
86 end = self._get_value(vtodo, "due") 87 if end: 88 return self._convert_to_datetime(end) 89 else: 90 raise KeyError("Start time not found")
91
92 - def _extract_todo_start(self, vtodo):
93 valarm = self._get_first_subelement(vtodo, "VALARM") 94 if valarm: 95 start = self._get_value(valarm, "trigger") 96 if start: 97 return self._convert_to_datetime(start)
98
99 - def _extract_todo_description(self, vtodo):
100 if self.add_location_to_description: 101 if "location" in vtodo: 102 return "%s: %s" % (_("Location"), vtodo["location"]) 103 else: 104 return None 105 else: 106 return None
107
108 - def _extract_todo_alert(self, vtodo):
109 if self.use_trigger_as_alert: 110 start = self._extract_todo_start(vtodo) 111 if start is not None: 112 return (start, "")
113
114 - def _read_calendar_object(self, file_contents):
115 try: 116 return Calendar.from_ical(file_contents) 117 except Exception as pe: 118 msg1 = _("Unable to read calendar data.") 119 msg2 = "\n\n" + ex_msg(pe) 120 raise TimelineIOError(msg1 + msg2)
121
122 - def _read_file_content(self, path):
123 ics_file = None 124 try: 125 ics_file = open(path, "rb") 126 return ics_file.read() 127 except IOError as e: 128 msg = _("Unable to read from file '%s'.") 129 whole_msg = (msg + "\n\n%s") % (abspath(path), e) 130 raise TimelineIOError(whole_msg) 131 finally: 132 if ics_file is not None: 133 ics_file.close()
134
135 - def _save_data_in_db(self, db):
136 for event in self.events: 137 db.save_event(event) 138 for category in self.categories: 139 db.save_category(category)
140
141 - def _load_vevent(self, db, vevent):
142 try: 143 start, end = self._extract_start_end(vevent) 144 txt = self._get_event_name(vevent) 145 event = Event().update(start, end, txt) 146 event.set_description(self._get_description(vevent)) 147 self.events.append(event) 148 except: 149 self.vevents_not_loaded.append((txt, start, end))
150
151 - def _load_categories(self, vevent):
152 if "categories" in vevent: 153 categories_names = [cat.strip() for cat in vevent["categories"].cats if len(cat.strip()) > 0] 154 for category_name in categories_names: 155 if category_name not in self.category_names: 156 self.categories.append(Category().update( 157 category_name, 158 self._get_random_color(), 159 None 160 )) 161 self.category_names.append(category_name)
162
163 - def _get_random_color(self):
164 return (random.randint(0, 255), 165 random.randint(0, 255), 166 random.randint(0, 255))
167
168 - def _get_event_name(self, vevent):
169 return self._get_first_value(vevent, ["summary", "description"], "")
170
171 - def _get_description(self, vevent):
172 if self.add_location_to_description: 173 if "location" in vevent: 174 sep = "" 175 if "description" in vevent: 176 sep = "\n\n" 177 return "%s%s%s: %s" % (self._get_value(vevent, "description", ""), 178 sep, 179 _("Location"), 180 self._get_value(vevent, "location", "")) 181 else: 182 return self._get_value(vevent, "description", None) 183 else: 184 return self._get_value(vevent, "description", None)
185
186 - def _get_first_value(self, element, key_list, not_found_value=None):
187 for key in key_list: 188 if key in element: 189 return element[key] 190 return not_found_value
191
192 - def _get_value(self, element, key, not_found_value=None):
193 if key in element: 194 return element.decoded(key) 195 return not_found_value
196
197 - def _extract_start_end(self, vevent):
198 start = self._convert_to_datetime(vevent.decoded("dtstart")) 199 if "dtend" in vevent: 200 end = self._convert_to_datetime(vevent.decoded("dtend")) 201 elif "duration" in vevent: 202 end = start + self._convert_to_timedelta(vevent.decoded("duration")) 203 else: 204 end = self._convert_to_datetime(vevent.decoded("dtstart")) 205 return (start, end)
206
207 - def _convert_to_timedelta(self, t):
208 if isinstance(t, timedelta): 209 return GregorianDelta(t.seconds) 210 else: 211 return GregorianDelta(0)
212
213 - def _convert_to_datetime(self, d):
214 if isinstance(d, datetime): 215 return GregorianDateTime(d.year, d.month, d.day, d.hour, d.minute, d.second).to_time() 216 elif isinstance(d, date): 217 return GregorianDateTime.from_ymd(d.year, d.month, d.day).to_time() 218 else: 219 raise TimelineIOError("Unknown date.")
220
221 - def _get_first_subelement(self, parent, subelement_name):
222 subelements = self._get_subelements(parent, subelement_name) 223 if len(subelements) > 0: 224 return subelements[0]
225
226 - def _get_subelements(self, parent, subelement_name):
227 return parent.walk(subelement_name)
228
230 if len(self.vevents_not_loaded) > 0: 231 message = ("Some events couldn't be loaded!\n" + 232 "The first 10 failing events are shown below.\n" + 233 self._format_vevents_not_loaded()) 234 display_warning_message(message)
235
237 return "\n".join(["Name='%s' Start=%s End=%s" % (txt, 238 self._time_to_text(start), 239 self._time_to_text(end)) for txt, start, end in self.vevents_not_loaded][:10])
240
241 - def _time_to_text(self, time):
242 d = "%d-%02d-%02d " % GregorianDateTime.from_time(time).to_date_tuple() 243 t = "%02d.%02d.%02d" % GregorianDateTime.from_time(time).to_time_tuple() 244 return d + t
245 246
247 -def import_db_from_ics(path, options_dialog=None, options=None):
248 global events 249 events = [] 250 if options is None: 251 options = [False, False, False] 252 db = MemoryDB() 253 db.set_readonly() 254 if options_dialog is not None: 255 dlg = options_dialog() 256 dlg.ShowModal() 257 options = (dlg.get_import_location(), 258 dlg.get_trigger_as_start_time(), 259 dlg.get_trigger_as_alarm()) 260 dlg.Destroy() 261 IcsLoader().load(db, path, options) 262 db.clear_transactions() 263 return db
264