def set_gtg(self, todo: iCalendar, task: Task, namespace: str = None) -> None: remote_tags = [self.to_tag(categ) for categ in self.get_dav(todo)] local_tags = set(tag_name for tag_name in super().get_gtg(task)) for to_add in set(remote_tags).difference(local_tags): task.add_tag(to_add) for to_delete in local_tags.difference(remote_tags): task.remove_tag(to_delete) task.tags.sort(key=remote_tags.index)
def test_due_date_caldav_restriction(self): task = Task('uid', Mock()) later = datetime(2021, 11, 24, 21, 52, 45) before = later - timedelta(days=1) task.set_start_date(later) task.set_due_date(before) field = DueDateField('due', 'get_due_date_constraint', 'set_due_date') self.assertEqual(later, field.get_gtg(task, '').dt_value) task.set_start_date(before) task.set_due_date(later) self.assertEqual(later, field.get_gtg(task, '').dt_value)
def get_gtg(self, task: Task, namespace: str = None): parents = task.get_parents() if not parents or not parents[0]: return parent = task.req.get_task(parents[0]) uid = UID_FIELD.get_gtg(task, namespace) return parent.get_child_index(uid)
def _set_task(self, task: Task) -> None: logger.debug('set_task todo for %r', task.get_uuid()) with DisabledSyncCtx(task, sync_on_exit=False): seq_value = SEQUENCE.get_gtg(task, self.namespace) SEQUENCE.write_gtg(task, seq_value + 1, self.namespace) todo, calendar = self._get_todo_and_calendar(task) if not calendar: logger.info("%r has no calendar to be synced with", task) return if todo and todo.parent.url != calendar.url: # switch calendar self._remove_todo(UID_FIELD.get_dav(todo), todo) self._create_todo(task, calendar) elif todo: # found one, saving it if not Translator.should_sync(task, self.namespace, todo): logger.debug('insufficient change, ignoring set_task call') return # updating vtodo content Translator.fill_vtodo(task, calendar.name, self.namespace, todo.instance.vtodo) logger.info('SYNCING updating todo %r', todo) try: todo.save() except caldav.lib.error.DAVError: logger.exception( 'Something went wrong while updating ' '%r => %r', task, todo) else: # creating from task self._create_todo(task, calendar)
def task_factory(self, tid, newtask=False): """ Instantiates the given task id as a Task object. @param tid: a task id. Must be unique @param newtask: True if the task has never been seen before @return Task: a Task instance """ return Task(tid, self.requester, newtask)
def fill_task(cls, todo: iCalendar, task: Task, namespace: str): nmspc = {'namespace': namespace} with DisabledSyncCtx(task): for field in cls.fields: field.set_gtg(todo, task, **nmspc) task.set_attribute("url", str(todo.url), **nmspc) task.set_attribute("calendar_url", str(todo.parent.url), **nmspc) task.set_attribute("calendar_name", todo.parent.name, **nmspc) if not CATEGORIES.has_calendar_tag(task, todo.parent): task.add_tag(CATEGORIES.get_calendar_tag(todo.parent)) return task
def _get_todo_and_calendar(self, task: Task): """For a given task, try to get the todo out of the cache and figures out its calendar if one is linked to it""" todo, calendar = self._cache.get_todo(UID_FIELD.get_gtg(task)), None # lookup by task for __, calendar in self._cache.calendars: if CATEGORIES.has_calendar_tag(task, calendar): logger.debug('Found from task tag %r and %r', todo, calendar) return todo, calendar cname = task.get_attribute('calendar_name', namespace=self.namespace) curl = task.get_attribute("calendar_url", namespace=self.namespace) if curl or cname: calendar = self._cache.get_calendar(name=cname, url=curl) if calendar: logger.debug('Found from task attr %r and %r', todo, calendar) return todo, calendar if todo and getattr(todo, 'parent', None): logger.debug('Found from todo %r and %r', todo, todo.parent) return todo, todo.parent return None, None
def test_translate_from_vtodo(self): DESCRIPTION = Translator.fields[1] self.assertEqual(DESCRIPTION.dav_name, 'description') todo = self._get_todo(VTODO_GRAND_CHILD) self.assertEqual(todo.instance.vtodo.serialize(), VTODO_GRAND_CHILD) self.assertEqual(date(2020, 12, 24), todo.instance.vtodo.contents['due'][0].value) uid = UID_FIELD.get_dav(todo) self.assertTrue(isinstance(uid, str), f"should be str is {uid!r}") self.assertEqual(uid, UID_FIELD.get_dav(vtodo=todo.instance.vtodo)) task = Task(uid, Mock()) Translator.fill_task(todo, task, NAMESPACE) self.assertEqual('date', task.get_due_date().accuracy.value) vtodo = Translator.fill_vtodo(task, todo.parent.name, NAMESPACE) for field in Translator.fields: if field.dav_name in DAV_IGNORE: continue self.assertTrue(field.is_equal(task, NAMESPACE, todo)) self.assertTrue(field.is_equal(task, NAMESPACE, vtodo=vtodo.vtodo)) vtodo.vtodo.contents['description'][0].value = 'changed value' self.assertTrue( DESCRIPTION.is_equal(task, NAMESPACE, todo), 'same ' 'hashes should prevent changes on vTodo to be noticed') task.set_text(task.get_text() + 'more content') self.assertFalse(DESCRIPTION.is_equal(task, NAMESPACE, todo))
def _extract_plain_text(self, task: Task) -> str: """Will extract plain text from task content, replacing subtask referenced in the text by their proper titles""" result, content = '', task.get_text() for line_no, line in enumerate(content.splitlines()): for tag in self.XML_TAGS: while tag in line: line = line.replace(tag, '') if line_no == 0: # is first line, striping all tags on first line new_line = self.__clean_first_line(line) if new_line: result += new_line + '\n' elif line.startswith('{!') and line.endswith('!}'): subtask = task.req.get_task(line[2:-2].strip()) if not subtask: continue if subtask.get_status() == Task.STA_DONE: result += f"[x] {subtask.get_title()}\n" else: result += f"[ ] {subtask.get_title()}\n" else: result += line.strip() + '\n' return result.strip()
def test_translate_from_task(self): now, today = datetime.now(), date.today() task = Task('uuid', Mock()) task.set_title('holy graal') task.set_text('the knights who says ni') task.set_recurring(True, 'other-day') task.set_start_date(today) task.set_due_date('soon') task.set_closed_date(now) vtodo = Translator.fill_vtodo(task, 'My Calendar Name', NAMESPACE) for field in Translator.fields: self.assertTrue(field.is_equal(task, NAMESPACE, vtodo=vtodo.vtodo), f'{field!r} has differing values') serialized = vtodo.serialize() self.assertTrue( f"DTSTART;VALUE=DATE:{today.strftime('%Y%m%d')}" in serialized, f"missing from {serialized}") self.assertTrue(re.search(r"COMPLETED:[0-9]{8}T[0-9]{6}Z", serialized), f"missing from {serialized}") self.assertTrue("DUE;GTGFUZZY=soon" in serialized, f"missing from {serialized}") # trying to fill utc only with fuzzy task.set_closed_date('someday') vtodo = Translator.fill_vtodo(task, 'My Calendar Name', NAMESPACE) serialized = vtodo.serialize() self.assertTrue("COMPLETED;GTGFUZZY=someday:" in serialized, f"missing from {serialized}") # trying to fill utc only with date task.set_closed_date(today) vtodo = Translator.fill_vtodo(task, 'My Calendar Name', NAMESPACE) serialized = vtodo.serialize() today_in_utc = now.replace(hour=0, minute=0, second=0)\ .replace(tzinfo=LOCAL_TIMEZONE).astimezone(UTC)\ .strftime('%Y%m%dT%H%M%SZ') self.assertTrue(f"COMPLETED:{today_in_utc}" in serialized, f"missing {today_in_utc} from {serialized}") # emptying date by setting None or no_date task.set_closed_date(Date.no_date()) task.set_due_date(None) task.set_start_date('') vtodo = Translator.fill_vtodo(task, 'My Calendar Name', NAMESPACE) serialized = vtodo.serialize() self.assertTrue("CATEGORIES:" not in serialized) self.assertTrue("COMPLETED:" not in serialized) self.assertTrue("DUE:" not in serialized) self.assertTrue("DTSTART:" not in serialized)
def get_gtg(self, task: Task, namespace: str = None) -> tuple: return task.get_recurring(), task.get_recurring_term()
def write_gtg(self, task: Task, value, namespace: str = None): hash_, text = value if hash_ and hash_ == self._get_content_hash(task.get_text()): logger.debug('not writing %r from vtodo, hash matches', task) return return super().write_gtg(task, text)
def write_gtg(self, task: Task, value, namespace: str = None): task.set_attribute(self.dav_name, value, namespace=namespace)
def get_gtg(self, task: Task, namespace: str = None) -> str: return task.get_attribute(self.dav_name, namespace=namespace)
def has_calendar_tag(self, task: Task, calendar: iCalendar) -> bool: return self.get_calendar_tag(calendar) in task.get_tags_name()
def _browse_subtasks(cls, task: Task): yield task for subtask in task.get_subtasks(): yield from cls._browse_subtasks(subtask)