In a round about way, fixed the setting the date issue (I hope)
authorEd Page <epage@Dulcinea.(none)>
Wed, 15 Apr 2009 02:06:16 +0000 (21:06 -0500)
committerEd Page <epage@Dulcinea.(none)>
Wed, 15 Apr 2009 02:06:16 +0000 (21:06 -0500)
src/doneit_glade.py
src/gtk_toolbox.py
src/rtm_backend.py
src/rtm_view.py
src/toolbox.py

index 31f2bfe..c34b843 100755 (executable)
@@ -14,6 +14,7 @@
                Drop down for multi selecting tags
                Drop down for multi selecting locations
                Calendar selector for choosing due date range
+@todo Add logging support to make debugging random user issues a lot easier
 """
 
 from __future__ import with_statement
index 1653bb0..c9a6ccf 100644 (file)
 
 import warnings
 import contextlib
+import datetime
 
 import gobject
 import gtk
 
+import toolbox
+
 
 @contextlib.contextmanager
 def gtk_lock():
@@ -219,10 +222,12 @@ class PopupCalendar(object):
                self.__popupWindow.set_transient_for(parent)
                self.__popupWindow.set_modal(True)
 
+               self.callback = lambda: None
+
        def get_date(self):
                year, month, day = self.__calendar.get_date()
                month += 1 # Seems to be 0 indexed
-               return year, month, day
+               return datetime.date(year, month, day)
 
        def run(self):
                self.__popupWindow.show_all()
@@ -282,16 +287,16 @@ class EditTaskDialog(object):
                originalProjectId = taskDetails["projId"]
                originalProjectName = todoManager.get_project(originalProjectId)["name"]
                originalName = taskDetails["name"]
-               try:
-                       originalPriority = int(taskDetails["priority"])
-               except ValueError:
-                       originalPriority = 0
-               originalDue = taskDetails["due"]
+               originalPriority = str(taskDetails["priority"].get_nothrow(0))
+               if taskDetails["dueDate"].is_good():
+                       originalDue = taskDetails["dueDate"].get().strftime("%Y-%m-%d %H:%M:%S")
+               else:
+                       originalDue = ""
 
                self._dialog.set_default_response(gtk.RESPONSE_OK)
                self._taskName.set_text(originalName)
                self._set_active_proj(originalProjectName)
-               self._priorityChoiceCombo.set_active(originalPriority)
+               self._priorityChoiceCombo.set_active(int(originalPriority))
                self._dueDateDisplay.set_text(originalDue)
 
                try:
@@ -319,10 +324,20 @@ class EditTaskDialog(object):
                        todoManager.set_name(taskId, newName)
                        print "NAME CHANGE"
                if isPriorityDifferent:
-                       todoManager.set_priority(taskId, newPriority)
+                       try:
+                               priority = toolbox.Optional(int(newPriority))
+                       except ValueError:
+                               priority = toolbox.Optional()
+                       todoManager.set_priority(taskId, priority)
                        print "PRIO CHANGE"
                if isDueDifferent:
-                       todoManager.set_duedate(taskId, newDueDate)
+                       if newDueDate:
+                               due = datetime.datetime.strptime(newDueDate, "%Y-%m-%d %H:%M:%S")
+                               due = toolbox.Optional(due)
+                       else:
+                               due = toolbox.Optional()
+
+                       todoManager.set_duedate(taskId, due)
                        print "DUE CHANGE"
 
                return {
@@ -362,14 +377,13 @@ class EditTaskDialog(object):
                else:
                        return str(index)
 
-       def _get_date(self):
-               # @bug The date is not used in a consistent manner causing ... issues
-               dateParts = self._popupCalendar.get_date()
-               dateParts = (str(part) for part in dateParts)
-               return "-".join(dateParts)
-
        def _update_duedate(self):
-               self._dueDateDisplay.set_text(self._get_date())
+               date = self._popupCalendar.get_date()
+               time = datetime.time()
+               dueDate = datetime.datetime.combine(date, time)
+
+               formttedDate = dueDate.strftime("%Y-%m-%d %H:%M:%S")
+               self._dueDateDisplay.set_text(formttedDate)
 
        def _on_name_paste(self, *args):
                clipboard = gtk.clipboard_get()
index 3949f1e..76c4ded 100644 (file)
@@ -2,7 +2,9 @@
 Wrapper for Remember The Milk API
 """
 
+import datetime
 
+import toolbox
 import rtm_api
 
 
@@ -80,45 +82,27 @@ class RtMilkManager(object):
                for realProjId, taskSeries in self._get_taskseries(projId):
                        for task in self._get_tasks(taskSeries):
                                taskId = self._pack_ids(realProjId, taskSeries.id, task.id)
-                               priority = task.priority if task.priority != "N" else ""
-                               yield {
+                               rawTaskDetails = {
                                        "id": taskId,
                                        "projId": projId,
                                        "name": taskSeries.name,
-                                       "url": fix_url(taskSeries.url),
+                                       "url": taskSeries.url,
                                        "locationId": taskSeries.location_id,
                                        "dueDate": task.due,
-                                       "isCompleted": len(task.completed) != 0,
-                                       "completedDate": task.completed,
-                                       "priority": priority,
-                                       "estimate": task.estimate,
-                                       "notes": list(self._get_notes(taskSeries.notes)),
-                               }
-
-       def get_task_details(self, taskId):
-               projId, seriesId, taskId = self._unpack_ids(taskId)
-               for realProjId, taskSeries in self._get_taskseries(projId):
-                       assert projId == realProjId, "%s != %s, looks like we let leak a metalist id when packing a task id" % (projId, realProjId)
-                       curSeriesId = taskSeries.id
-                       if seriesId != curSeriesId:
-                               continue
-                       for task in self._get_tasks(taskSeries):
-                               curTaskId = task.id
-                               if task.id != curTaskId:
-                                       continue
-                               return {
-                                       "id": taskId,
-                                       "projId": realProjId,
-                                       "name": taskSeries.name,
-                                       "url": fix_url(taskSeries.url),
-                                       "locationId": taskSeries.location_id,
-                                       "due": task.due,
                                        "isCompleted": task.completed,
                                        "completedDate": task.completed,
                                        "priority": task.priority,
                                        "estimate": task.estimate,
-                                       "notes": list(self._get_notes(taskSeries.notes)),
+                                       "notes": list(self._get_notes(taskId, taskSeries.notes)),
                                }
+                               taskDetails = self._parse_task_details(rawTaskDetails)
+                               yield taskDetails
+
+       def get_task_details(self, taskId):
+               projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
+               for task in self.get_tasks_with_details(projId):
+                       if task["id"] == taskId:
+                               return task
                return {}
 
        def add_task(self, projId, taskName):
@@ -157,6 +141,10 @@ class RtMilkManager(object):
                assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
 
        def set_duedate(self, taskId, dueDate):
+               assert isinstance(dueDate, toolbox.Optional), (
+                       "Date being set too definitively: %r" % dueDate
+               )
+
                projId, seriesId, taskId = self._unpack_ids(taskId)
                rsp = self._rtm.tasks.setDueDate(
                        timeline=self._timeline,
@@ -169,10 +157,10 @@ class RtMilkManager(object):
                assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
 
        def set_priority(self, taskId, priority):
-               if priority is None:
-                       priority = "N"
-               else:
-                       priority = str(priority)
+               assert isinstance(priority, toolbox.Optional), (
+                       "Priority being set too definitively: %r" % priority
+               )
+               priority = str(priority.get_nothrow("N"))
                projId, seriesId, taskId = self._unpack_ids(taskId)
 
                rsp = self._rtm.tasks.setPriority(
@@ -245,7 +233,66 @@ class RtMilkManager(object):
                for task in tasks:
                        yield task
 
-       def _get_notes(self, notes):
+       def _parse_task_details(self, rawTaskDetails):
+               taskDetails = {}
+               taskDetails["id"] = rawTaskDetails["id"]
+               taskDetails["projId"] = rawTaskDetails["projId"]
+               taskDetails["name"] = rawTaskDetails["name"]
+               taskDetails["url"] = fix_url(rawTaskDetails["url"])
+
+               rawLocationId = rawTaskDetails["locationId"]
+               if rawLocationId:
+                       locationId = toolbox.Optional(rawLocationId)
+               else:
+                       locationId = toolbox.Optional()
+               taskDetails["locationId"] = locationId
+
+               if rawTaskDetails["dueDate"]:
+                       dueDate = datetime.datetime.strptime(
+                               rawTaskDetails["dueDate"],
+                               "%Y-%m-%dT%H:%M:%SZ",
+                       )
+                       dueDate = toolbox.Optional(dueDate)
+               else:
+                       dueDate = toolbox.Optional()
+               taskDetails["dueDate"] = dueDate
+
+               taskDetails["isCompleted"] = len(rawTaskDetails["isCompleted"]) != 0
+
+               if rawTaskDetails["completedDate"]:
+                       completedDate = datetime.datetime.strptime(
+                               rawTaskDetails["completedDate"],
+                               "%Y-%m-%dT%H:%M:%SZ",
+                       )
+                       completedDate = toolbox.Optional(completedDate)
+               else:
+                       completedDate = toolbox.Optional()
+               taskDetails["completedDate"] = completedDate
+
+               try:
+                       priority = toolbox.Optional(int(rawTaskDetails["priority"]))
+               except ValueError:
+                       priority = toolbox.Optional()
+               taskDetails["priority"] = priority
+
+               if rawTaskDetails["estimate"]:
+                       estimate = rawTaskDetails["estimate"]
+                       estimate = toolbox.Optional(estimate)
+               else:
+                       estimate = toolbox.Optional()
+               taskDetails["estimate"] = estimate
+
+               taskDetails["notes"] = rawTaskDetails["notes"]
+
+               rawKeys = list(rawTaskDetails.iterkeys())
+               rawKeys.sort()
+               parsedKeys = list(taskDetails.iterkeys())
+               parsedKeys.sort()
+               assert rawKeys == parsedKeys, "Missing some, %r != %r" % (rawKeys, parsedKeys)
+
+               return taskDetails
+
+       def _get_notes(self, taskId, notes):
                if not notes:
                        return
                elif isinstance(notes.note, list):
@@ -253,12 +300,14 @@ class RtMilkManager(object):
                else:
                        notes = (notes.note, )
 
+               projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
+
                for note in notes:
-                       id = note.id
+                       noteId = self._pack_ids(projId, rtmSeriesId, rtmTaskId, note.id)
                        title = note.title
                        body = getattr(note, "$t")
                        yield {
-                               "id": id,
+                               "id": noteId,
                                "title": title,
                                "body": body,
                        }
index 3989a90..efdf60a 100644 (file)
@@ -290,12 +290,13 @@ class GtkRtMilk(object):
                        id = taskDetails["id"]
                        isCompleted = taskDetails["isCompleted"]
                        name = abbreviate(taskDetails["name"], 100)
-                       priority = taskDetails["priority"]
-                       dueDescription = taskDetails["dueDate"]
-                       if dueDescription:
-                               dueDate = datetime.datetime.strptime(dueDescription, "%Y-%m-%dT%H:%M:%SZ")
+                       priority = str(taskDetails["priority"].get_nothrow(""))
+                       if taskDetails["dueDate"].is_good():
+                               dueDate = taskDetails["dueDate"].get()
+                               dueDescription = dueDate.strftime("%Y-%m-%d %H:%M:%S")
                                fuzzyDue = toolbox.to_fuzzy_date(dueDate)
                        else:
+                               dueDescription = ""
                                fuzzyDue = ""
 
                        linkDisplay = taskDetails["url"]
index e6dfcbf..911555d 100644 (file)
@@ -5,6 +5,77 @@ from xml.dom import minidom
 import datetime
 
 
+class Optional(object):
+       """
+       Taglines:
+       Even you don't have to worry about knowing when to perform None checks
+       When the NULL object pattern just isn't good enough
+
+       >>> a = Optional()
+       >>> a.is_good()
+       False
+       >>> a.get_nothrow("Blacksheep")
+       'Blacksheep'
+       >>> a.set("Lamb")
+       >>> a.is_good()
+       True
+       >>> a.get_nothrow("Blacksheep"), a.get(), a()
+       ('Lamb', 'Lamb', 'Lamb')
+       >>> a.clear()
+       >>> a.is_good()
+       False
+       >>> a.get_nothrow("Blacksheep")
+       'Blacksheep'
+       >>>
+       >>> b = Optional("Lamb")
+       >>> a.set("Lamb")
+       >>> a.is_good()
+       True
+       >>> a.get_nothrow("Blacksheep"), a.get(), a()
+       ('Lamb', 'Lamb', 'Lamb')
+       >>> a.clear()
+       >>> a.is_good()
+       False
+       >>> a.get_nothrow("Blacksheep")
+       'Blacksheep'
+       """
+
+       class NonExistent(object):
+               pass
+
+       def __init__(self, value = NonExistent):
+               self._value = value
+
+       def set(self, value):
+               self._value = value
+
+       def clear(self):
+               self._value = self.NonExistent
+
+       def is_good(self):
+               return self._value is not self.NonExistent
+
+       def get_nothrow(self, default = None):
+               return self._value if self.is_good() else default
+
+       def get(self):
+               if not self.is_good():
+                       raise ReferenceError("Optional not initialized")
+               return self._value
+
+       def __call__(self):
+               # Implemented to imitate weakref
+               return self.get()
+
+       def __str__(self):
+               return str(self.get_nothrow(""))
+
+       def __repr__(self):
+               return "<Optional at %x; to %r>" % (
+                       id(self), self.get_nothrow("Nothing")
+               )
+
+
 def open_anything(source, alternative=None):
        """URI, filename, or string --> stream