6104 comments
2357 subscribers
6264 on Twitter
Subscribe! Feed reader E-mail

On this page:

Proactive communication: Five tips for following up

Isabelle’s manager wanted her to get better at proactive communication. She’s comfortable e-mailing people, but she has a hard time following up when people don’t respond. Timezone differences between team members in Singapore and in the US compound delays. She reached out to me for advice, and I suggested a few things that might help:

1. Clear, dated requests. When asking for help or a response through e-mail, specify a target date instead of leaving it open-ended, and give a reason for that date if possible. This makes it easier for people to prioritize working on your task. (Don’t always ask people to get back to you TODAY, though. It looks like you don’t plan well.)

2. Clear, dated responses and priorities. If you’re working with other people on some lower-priority tasks, those tasks might never be finished. Clarify the relative priority of a task with your manager: it might turn out to be higher-priority than you thought. If it really is a low-priority project, contact the people you need to collaborate with and get an estimate of when they might be able to work on their part of the project. Find out what other important projects they’re working on, too. This will allow you to:

  • give clearer reasons for delays (“We can only work on the report next week because we have to finish the keynote presentation this week”)
  • negotiate better solutions (“I can do that part of the presentation if you can do this part of the report”)
  • re-negotiate priorities with your manager (“Actually, this report is more important than adding animation to the presentation”), and
  • give you dates for following up (“John is working on the presentation now, but he promised to work on the report on Monday, and I’ll follow up with him then”).

3. Status reports. They’re good for your manager and for you. Keep track of where you are on projects: what your next actions are, what you’re waiting for, and what you’ve accomplished. Share this with your manager frequently, so there are no surprises.

4. Concrete follow-ups. When you’re waiting for a response, schedule a follow-up so that it doesn’t slip through the cracks. Follow up by e-mail, and then move up to following up by phone or instant message if needed. I don’t do this for all of my tasks, but I do this for tasks I “own,” and it helps.

Concrete follow-up dates also help you write better status reports. Instead of reporting “Waiting for response”, you can report “Waiting for response; will follow up on ____ by e-mail and _____ by phone.” Clear follow-up plans make people feel more confident that the task won’t be forgotten.

5. Tactful escalation. When people don’t respond, sometimes you need to find other ways to get things going. Isabelle had learned how to cc:ing her manager so that her manager could stay updated, but she wasn’t comfortable with cc:ing the other person’s manager because it felt like escalation. If done tactfully, though, escalation can be a good tool.

How to escalate: Give people the benefit of the doubt, and acknowledge that they might be busy working on priority projects. Send them a gentle reminder, cc:ing their manager. In the note, explain to the manager that you understand that the original contact may be busy or your request might be a better fit for someone on the team, and ask who might be the best person to talk to.

Hope that helps!

2010-08-24 Tue 10:20

Short URL: http://sachachua.com/blog/p/7320

Note-taking revisited

I was away for training last week, attending a 3-day learning session organized by IBM. There were around 500 IBMers there. My manager not only suggested that I go, he even gave me a lift. I resolved to make the most of it.

Packing light meant taking my work laptop, leaving my netbook, and bringing a small paper notebook along as a backup for note-taking. I like taking notes. I’d rather slow down and take notes than waste the time and the opportunity by forgetting.

In 2006, I wrote about how taking notes during conversations helps with post-event connection. What’s changed in the last four years? I now take casual notes on my iPod Touch. I’ve been thinking about getting a tablet PC for better note-taking. But for fast-flowing conversations, I still return to paper.

I’ve rediscovered drawing. My notes are punctuated by doodles: quick sketches of presenters, random objects that suggest themselves to a wandering right-brain. I like drawing. It helps me remember what a session felt like, instead of just what it contained.

I no longer bring fountain pens, as they’re all too easy to drop. Instead, I use a fine-point gel pen, which is clearer than pencils when it comes to scanning or review, and which writes more smoothly than a ballpoint pen does. I use a multi-colour ballpoint pen for review and emphasis.

My workflow has improved. While taking notes, I mark action items with a square on the left, particularly interesting topics with a star, ideas with a lightbulb, and thoughts and reflections with a thoughtcloud. This makes it easy to skim my notes for action items during review.

Instead of trying to hold the notebook open as I type thoughts in, I scan new pages at 600dpi full colour. This gives me a digital backup that I can flip through on my computer while I type my notes on a separate screen. As I type, I copy my action items into a separate section. After I finish writing my notes, I review the action items and import them into my task manager.

How can I make this even better?

I can write more neatly. This means slowing down in the beginning, but it will save me time when skimming or reading my notes. (And if I do it really well, maybe Evernote can understand my handwriting!)

I can try using a pad and then scan sheets using the automatic document feeder. Our printer/scanner’s automatic document feeder scans only one side, but I can simply do two passes. This would reduce scanning time.

I can save up for a tablet and see if that works out better for note-taking. I like being able to draw diagrams and icons while taking notes, so it would be good to experiment with a Tablet PC.

Short URL: http://sachachua.com/blog/p/7311

Long weekend reflections

Long weekends? We don’t pack our bags and travel, or kick our feet up and chill out at our (non-existent) cottage. We work. House-work, life-work, all the projects where investing hours of concentrated time pays.

On Monday, we played a real-life puzzle game in our living room, moving tables and boxes around until we could reorganize everything the way we wanted it. Moved the bicycles to the deck, followed by the woodworking tools and materials. With the floor-space cleared (at least a little), disassembled the table and rolled it out to the porch for donation. Moved the bric-a-brac hiding behind the table to their rightful places, then moved the piano into the space formerly occupied by the table. Moved the coffee table aside. Moved the couch to the space occupied by the piano. With access to the shelves, moved all the books off the back bookshelf, then moved the bookcase to the opposite wall. Moved the bicycles into the space formerly occupied by the bookcase. Moved the books back onto the bookcase, setting aside many books for donation. Moved books from the second bookcase onto the first bookcase and the third bookcase, again setting aside more books. Moved the now-empty second bookcase beside the first bookcase. Moved the third bookcase’s books to the second bookcase, again setting aside books for donation. Moved the third bookcase into the kitchen. Moved the canned goods, baking supplies, and other shelf-stable ingredients from the basement to the bookcase-turned-pantry.

It felt awesome.

On Tuesday, we worked on our Adirondack chairs. I disassembled my chair and sanded the parts to prepare it for painting, while W- and J- worked on J-‘s chair. They were quite a while from painting, so I scanned my sketchbook and tweaked my digital filing system in order to be able to review my drawings more easily.

Our two-week vacation last year? Cooking, canning, sewing, gardening, biking, writing.

After something like that, work feels like an equal pleasure—perhaps even a relaxing treat. It’s not the sharp contrast between idyllic perfection and the cubicle grind, or exotic thrills and mundane routines.

And relaxation? A good day’s work, a good meal’s satisfaction, a good night’s sleep. Self-control to let neither work nor hobbies overrun each other. Love and laughter sprinkled throughout the day.

Why do I write this?

To share that it’s okay if your idea of a great vacation doesn’t involve vacating.

That life doesn’t have to be predicated on a dread of Mondays and a desire for escape.

That house-work and life-work done with intention and love can be fulfilling.

Short URL: http://sachachua.com/blog/p/7301

Keeping track of multiple projects

How do you keep sane when juggling multiple projects? For me, a to-do list and a way to organize project-related information make life so much better.

I’m planning two Idea Labs and an expertise location pilot, supporting a training course, assisting with a proposal, helping with two workshops, answering questions related to four potential Idea Labs, and adding to my community toolkit.

The key challenges are:

  • keeping track of all the actions I need to take and by when, so that I can estimate my workload and give people more accurate feedback
  • organizing information so that I can find and share it
  • following up on what I’ve asked other people to do

Toodledo helps me stay focused on what I need to do at work and at home. Capturing all the different things I need to do and making sure that due dates are written down means I don’t have to stress out about things falling through the cracks.

The Lotus Notes Activities sidebar lets me organize project information and refer to past discussions. Activities also makes it easy for me to add other people and share resources with them.

For following up, I’m getting used to creating tasks representing things I’m waiting for, and regularly reviewing this.

How do you eat an elephant sandwich?

One bite at a time.

Short URL: http://sachachua.com/blog/p/7252

Org-toodledo

I finally got around to asking my manager for permission to contribute org-toodledo as open source. Here it is. Enjoy!

;;; org-toodledo.el - Toodledo integration for Emacs Org mode
;; (c) 2010 Sacha Chua (sacha@sachachua.com)
;;
;; This file is not part of GNU Emacs.

;; This is free software; you can redistribute it and/or modify it under
;; the terms of the GNU General Public License as published by the Free
;; Software Foundation; either version 2, or (at your option) any later
;; version.
;;
;; This is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
;; FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
;; for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
;; MA 02111-1307, USA.

;; How to use:
;; 1. Customize org-toodledo-userid and org-toodledo-password
;; 2. Open a blank org file.
;; 3. Call org-toodledo-initialize-org
;; Call org-toodledo-update to bring in new/updated tasks (skips locally modified tasks newer than updated)
;; Call org-toodledo-sync-task to create or update the current task
;; Call org-toodledo-delete-current-task to delete the current task
;;
;; Doesn't do lots of error trapping. Might be a good idea to version-control your Org file.
;;
;; TOODLEDO ATTRIBUTES and how they are bi-directionally handled
;; Context: Handled by tags (ex:   :@work:  :@errands:)
;;   - will create new contexts if necessary
;; Task status: Mapped to TODO state.
;;   See org-toodledo-status-to-string and org-toodledo-parse-current-task for the mapping
;;   You will probably want something like this in your ~/.emacs:
;; (setq org-todo-keywords
;;      '((sequence
;;         "TODO(t)"  ; next action
;;         "PLAN(-)"
;;         "STARTED(s)"
;;         "WAITING(w@/!)"
;;         "POSTPONED(p)" "SOMEDAY(s@/!)" "|" "DONE(x!)" "CANCELLED(c@)")
;;      (type "DELEGATED(d@!)" "DONE(x)")))
;; Length: Mapped to effort
;; Priority: Mapped to [#A], [#B], or [#C]. (TODO: Change this to five levels of priority to match Toodledo)
;; Start date: Mapped to "SCHEDULED"
;; Due date: Mapped to "DEADLINE"
;; Tags: Mapped to tags
;; Note: Mapped to todo text. May get confused by asterisks, so don't use any starting asterisks in your body text.
;;   (or anything that looks like an Org headline).
;; Completed: Mapped to DONE todo state.
;;
;; TODO:
;; - [ ] Double-check new/changed/deleted task updating, still seems buggy
;; - [ ] Test, test, test - maybe make test harness?
;; - [ ] Move status<->string mapping to a variable - lookups are better than logic
;; - [ ] Make sure sync timestamps aren't getting updated more often than needed
;; - [ ] Suggest some kind of hook to make it easier to mark a task as locally modified

(require 'org)
(require 'w3m)
(require 'xml)
(defcustom org-toodledo-userid ""
  "UserID from Toodledo: http://www.toodledo.com/info/api_doc.php"
  :group 'org-toodledo
  :type 'string)

(defcustom org-toodledo-password ""
  "Password for Toodledo."
  :group 'org-toodledo
  :type 'string)

(defvar org-toodledo-token-expiry nil "Expiry time for authentication token.")
(defvar org-toodledo-token nil "Authentication token.")
(defvar org-toodledo-key nil "Authentication key.")

(require 'url)
(require 'url-http)

(defun org-toodledo-initialize-org ()
  "Replace buffer contents with Toodledo tasks."
  (interactive)
  (delete-region (point-min) (point-max))
  (let ((account-info (org-toodledo-get-account-info))
        (server-info (org-toodledo-get-server-info))
        (tasks (org-toodledo-get-tasks '(("notcomp" . "1")))))
    (insert "* Toodledo\n"
            ":PROPERTIES:\n"
            ":Last-modified: " (cdr (assoc "lastaddedit" account-info)) "\n"
            ":Last-deleted: " (cdr (assoc "lastdelete" account-info)) "\n"
            ":Last-sync: " (cdr (assoc "unixtime" server-info)) "\n"
            ":END:\n")
    (insert (mapconcat 'org-toodledo-task-to-string tasks "\n"))))

(defun org-toodledo-get-token ()
  "Retrieve authentication token valid for four hours."
  (if (and org-toodledo-token
           org-toodledo-token-expiry
           (time-less-p (current-time) org-toodledo-token-expiry))
      org-toodledo-token
    ;; Else retrieve a new token
    (let ((response
            (with-current-buffer
                (url-retrieve-synchronously
                 (concat "http://api.toodledo.com/api.php?method=getToken;userid="
                         org-toodledo-userid))
              (xml-parse-region (point-min) (point-max)))))
      (if (equal (car (car response)) 'error)
          (progn
            (setq org-toodledo-token nil
                  org-toodledo-key nil
                  org-toodledo-token-expiry nil)
            (error "Could not log in to Toodledo: %s" (elt (car response) 2)))
        (setq org-toodledo-token
              (elt (car response) 2))
        (setq org-toodledo-key (org-toodledo-key)
              ;; Set the expiry time
              org-toodledo-token-expiry
              (seconds-to-time
               (+ (time-to-seconds (current-time))
                  (* 60 60 4)))))   ;; four hours
      org-toodledo-token)))

(defun org-toodledo-key ()
  "Return authentication key used for each request."
  (if (and org-toodledo-token
           org-toodledo-token-expiry
           (time-less-p (current-time) org-toodledo-token-expiry)
           org-toodledo-key)
      org-toodledo-key
    (setq org-toodledo-key
          (md5 (concat (md5 org-toodledo-password)
                       org-toodledo-token
                       org-toodledo-userid)))))

(defun org-toodledo-get-url (method-name &optional params)
  "Return URL for METHOD-NAME and PARAMS."
  (org-toodledo-get-token)
  (concat "http://api.toodledo.com/api.php?method="
          (w3m-url-encode-string method-name)
          ";key=" (org-toodledo-key)
          (if params
              (concat
               ";"
               (mapconcat (lambda (x)
                            (concat
                             (w3m-url-encode-string (car x)) "="
                             (w3m-url-encode-string (cdr x))))
                          params
                          ";"))
            "")))

(defun org-toodledo-call-method (method-name &optional params)
  "Call METHOD-NAME with PARAMS and return the parsed XML."
  (setq params (cons (cons "unix" "1") params))
  (with-current-buffer
      (url-retrieve-synchronously
       (org-toodledo-get-url method-name params))
    (xml-parse-region (point-min) (point-max))))

(defmacro org-toodledo-defun (function-name api-name description)
  `(defun ,function-name (params)
     ,description
     (org-toodledo-call-method ,api-name params)))

(defun org-toodledo-get-server-info ()
  "Return server information."
  (org-toodledo-convert-xml-result-to-alist
    (car (org-toodledo-call-method "getServerInfo"))))

(defun org-toodledo-get-account-info ()
  "Return server information."
  (org-toodledo-convert-xml-result-to-alist
   (car (org-toodledo-call-method "getAccountInfo"))))

(org-toodledo-defun org-toodledo-add-task "addTask" "Add task with PARAMS.")
(org-toodledo-defun org-toodledo-edit-task "editTask" "Edit task with PARAMS.")
(org-toodledo-defun org-toodledo-delete-task "deleteTask" "Delete task with PARAMS.")

;; (setq temp (org-toodledo-get-tasks '(("notcomp" . "1"))))
;; (setq server-info (org-toodledo-get-server-info))
;; (setq account-info (org-toodledo-get-account-info))
(defun org-toodledo-convert-xml-result-to-alist (info)
  "Convert INFO to an alist."
  (delq nil
        (mapcar
         (lambda (item)
           (if (listp item)
               (cons (symbol-name (car item)) (elt item 2))))
         (xml-node-children (delete "\n\t" info)))))

(defun org-toodledo-get-tasks (&optional params)
  "Retrieve tasks using PARAMS.
Return a list of task alists."
  (mapcar
   'org-toodledo-convert-xml-result-to-alist
   (xml-get-children
    (car (org-toodledo-call-method "getTasks" params))
    'task)))

(defun org-toodledo-get-deleted (&optional params)
  "Retrieve deleted tasks using PARAMS.
Return a list of task alists."
  (mapcar
   'org-toodledo-convert-xml-result-to-alist
   (xml-get-children
    (car (org-toodledo-call-method "getDeleted" params))
    'task)))

(defun org-toodledo-entry-note ()
  "Extract the note for this entry."
  (save-excursion
    (org-back-to-heading)
    (when (looking-at org-complex-heading-regexp)
      (goto-char (match-end 0))
      (let ((text (buffer-substring-no-properties
                   (point)
                   (if (re-search-forward org-complex-heading-regexp nil t)
                       (match-beginning 0)
                     (org-end-of-subtree)))))
        (with-temp-buffer
          (insert text)
          (goto-char (point-min))
          (when (re-search-forward
                 (concat "\\<"
                         (regexp-quote org-deadline-string) " +<[^>\n]+>[ \t]*") nil t)
            (replace-match ""))
          (goto-char (point-min))
          (when (re-search-forward
                 (concat "\\<"
                         (regexp-quote org-scheduled-string) " +<[^>\n]+>[ \t]*") nil t)
            (replace-match ""))
          (goto-char (point-min))
          (while (re-search-forward "\n\n+" nil t)
            (replace-match "\n"))
          (org-export-remove-or-extract-drawers org-drawers nil nil)
          (buffer-substring-no-properties (point-min)
                                          (point-max)))))))

(defun org-toodledo-parse-current-task ()
  "Extract the status and Toodledo ID of the current task."
  (save-excursion
    (org-back-to-heading t)
    (when (and (looking-at org-complex-heading-regexp)
               (match-string 2)) ;; TODO
      (let* (info
             (status (match-string-no-properties 2))
             (priority (match-string-no-properties 3))
             (title (match-string-no-properties 4))
             (tags (match-string-no-properties 5))
             (id (org-entry-get (point) "Toodledo-ID"))
             (contexts (org-toodledo-get-contexts))
             context)
        ;; (add-to-list 'info (cons "title" (match-string-no-properties 1)))
        (if id (add-to-list 'info (cons "id" id)))
        (when tags
          (setq tags
              (delq nil
                    (mapcar
                     (lambda (tag)
                       (if (> (length tag) 0)
                           (if (string-match (org-re "@\\([[:alnum:]_]+\\)") tag)
                               (progn
                                 ;; Not recognized context
                                 (if (null (assoc (match-string 1 tag) contexts))
                                     ;; Create it if it does not yet exist
                                     (let ((result
                                            (org-toodledo-call-method
                                             "addContext"
                                             (list (cons "title" (match-string 1 tag))))))
                                       (if (eq (caar result) 'added)
                                           (setq org-toodledo-contexts
                                                 (cons (cons (match-string 1 tag)
                                                             (elt (car result) 2))
                                                       org-toodledo-contexts)
                                                 contexts org-toodledo-contexts))))
                                   ;; Get the ID of the context
                                 (setq context
                                       (cdr (assoc (match-string 1 tag) contexts)))
                                 nil)
                             tag)))
                     (split-string tags ":")))))
        (setq info
              (list
               (cons "id" id)
               (cons "title" title)
               (cons "length" (org-entry-get (point) "Effort"))
               (cons "context" context) 
               (cons "tag" (mapconcat 'identity tags " "))
               (cons "completed" (if (equal status "DONE") "1" "0"))
               (cons "status"
                     (cond 
                      ((equal status "STARTED") "2")
                      ((equal status "DELEGATED") "4")
                      ((equal status "SOMEDAY") "8")
                      ((equal status "CANCELLED") "9")
                      ((equal status "PLAN") "3")
                      ((equal status "WAITING") "5")                      
                      ((equal status "TODO") "1")))
               (cons "priority"
                     (cond
                      ((equal priority "[#A]") "2")
                      ((equal priority "[#B]") "1")
                      ((equal priority "[#C]") "0")))
               (cons "note"
                     (org-toodledo-entry-note))))
        (when (org-entry-get nil "DEADLINE")
          (setq info (cons (cons "duedate"
                                 (substring (org-entry-get nil "DEADLINE")
                                            0 10)) info)))
        (when (org-entry-get nil "SCHEDULED")
          (setq info (cons (cons "startdate"
                                 (substring (org-entry-get nil "SCHEDULED")
                                            0 10)) info)))
        info))))

(defun org-toodledo-sync ()
  "Synchronize all tasks."
  ;; Retrieve all tasks
  ;; For each task in the current buffer
  ;;   Synchronize an existing task that has changed
   (let ((regexp (concat "^\\*+[ \t]+\\(" org-todo-regexp "\\)")))
    (goto-char (point-min))
    (while (re-search-forward regexp nil t)
      (org-toodledo-sync-task))))

(defun org-toodledo-update ()
  "Insert new tasks and update previous tasks."
  (interactive)
  (let* ((server-info (org-toodledo-get-server-info))
         (account-info (org-toodledo-get-account-info))
         (changed (org-toodledo-account-changed account-info))
         (last-deleted (string-to-number (or (org-entry-get-with-inheritance "Last-deleted") "0")))
         (last-modified (string-to-number (or (org-entry-get-with-inheritance "Last-modified") "0")))
         (last-update (string-to-number (or (org-entry-get-with-inheritance "Last-sync") "0")))
         processed)
    ;; If tasks have been deleted or modified, then the Toodledo API
    ;; will give us the timestamps. We need to find out which tasks
    ;; have been deleted or modified since the last time we retrieved
    ;; the list of tasks that have been deleted or modified. We store
    ;; the last times in the properties of the root element.
    
    (if (and (assoc "deleted" changed) ;; Tasks have been deleted
             (>= (string-to-number (cdr (assoc "deleted" changed))) last-deleted))
        (setq processed
              (append (org-toodledo-process-deleted-tasks
                       last-deleted)
                       processed)))
    (if (and (assoc "modified" changed) ;; Tasks have been added or edited
             (>= (string-to-number (cdr (assoc "modified" changed)))
                last-modified))
        ;; Retrieve added/modified tasks
        (setq processed (append
                         (org-toodledo-process-modified-tasks last-modified) processed)))
    ;; TODO Look for tasks that were modified locally since the last synchronization
    (org-toodledo-process-locally-modified-tasks last-update processed)
    ;; TODO Update timestamps here
    (goto-char (point-min))
    (when (re-search-forward (concat "^\\(" outline-regexp "\\)") nil t)
      (org-entry-put (point)
                     "Last-sync"
                     (cdr (assoc "unixtime" server-info)))
      (when (assoc "lastaddedit" account-info)
        (org-entry-put (point)
                       "Last-modified"
                       (cdr 
                        (assoc "lastaddedit" account-info))))
      (when (assoc "lastdelete" account-info)
        (org-entry-put (point)
                         "Last-deleted"
                         (cdr
                          (assoc "lastdelete" account-info)))))))

(defun org-toodledo-process-locally-modified-tasks (last-update processed)
  "Synchronize tasks that were locally modified after LAST-UPDATE.
Skip tasks with IDs in PROCESSED."
  (goto-char (point-min))
  (let ((start (float-time (current-time))))
    (while (re-search-forward org-complex-heading-regexp nil t)
      ;; Look for all tasks in this buffer
      (if (match-string 2)
          ;; Is it a new task, or has it been modified since the last update?
          (let ((id (org-entry-get (point) "Toodledo-ID"))
                (modified (string-to-number (or (org-entry-get (point) "Modified") "")))
                (last-sync (if (org-entry-get (point) "Sync")
                               (string-to-number (org-entry-get (point) "Sync"))
                             0)))
            (if (or (null id)
                    (and (> modified last-sync)
                         (< modified start)
                         (not (member id processed))))
                (save-excursion (org-toodledo-sync-task))))))))

(defun org-toodledo-touch ()
  "Update the current task."
  (interactive)
  (org-entry-put (point) "Modified" (format "%d" (float-time (current-time)))))

(defvar org-toodledo-actually-delete t)
(defun org-toodledo-process-deleted-tasks (timestamp)
  "Remove tasks deleted after TIMESTAMP."
  (delq nil
        (mapcar
         (lambda (task)
           (when (org-toodledo-find-task task)
             (if org-toodledo-actually-delete
                 (delete-region (org-back-to-heading)
                                (if (re-search-forward org-complex-heading-regexp nil t)
                                    (match-beginning 0)
                                  (org-end-of-subtree)))
               (org-entry-delete (point) "Toodledo-ID")
               (org-entry-put (point) "Toodledo-Deleted" (timestamp)))
             (org-toodledo-task-id task)))
         (org-toodledo-get-deleted
          (list (cons "after" (number-to-string timestamp)))))))
  
(defun org-toodledo-process-modified-tasks (modified)
  "Handle all the tasks that have been modified since MODIFIED."
  (delq nil
        (mapcar
         (lambda (task)
           (if (org-toodledo-find-task task)
               (if (null (org-toodledo-update-task task modified))
                   (org-toodledo-task-id task))
             (org-toodledo-create-task task)))
         (org-toodledo-get-tasks (list (cons "modafter" (number-to-string modified)))))))



(defun org-toodledo-create-task (task)
  "Create a task for TASK."
  (goto-char (point-max))
  (if (point-at-eol) (insert "\n"))
  (insert (org-toodledo-task-to-string task))
  (org-toodledo-task-id task))

(defun org-toodledo-find-task (task)
  "Find the task specified by TASK."
  (goto-char (point-min))
  (re-search-forward
   (concat "^[ \t]*:Toodledo-ID:[ \t]+" (org-toodledo-task-id task) "$")
   nil t))
  
(defun org-toodledo-account-changed (account-info)
  "Return non-nil if the account has changed since the last check.
The result will be an alist of (\"modified\" . \"timestamp\") if tasks have
been added/edited and (\"deleted\" . \"timestamp\") if tasks have been deleted."
  (let ((last-modified (org-entry-get-with-inheritance "Last-modified"))
        (last-deleted (org-entry-get-with-inheritance "Last-deleted"))
        result)
    (if (> (string-to-number (or (cdr (assoc "lastaddedit" account-info)) "0"))
           (string-to-number (or last-modified "0")))
        (add-to-list 'result (cons "modified" last-modified)))
    (if (> (string-to-number (or (cdr (assoc "lastdelete" account-info)) ""))
           (string-to-number (or last-deleted "0")))
        (add-to-list 'result (cons "deleted" last-deleted)))
    result))
  
(defun org-toodledo-sync-task (&optional force)
  "Update my Toodledo for the current task."
  (interactive "P")
  (save-excursion
    (let ((task (org-toodledo-parse-current-task)))
      (if (null (org-toodledo-task-id task))
          ;; New task, create it
          (let ((result (org-toodledo-add-task task)))
            (when (eq (elt (car result) 0) 'added)
              (org-entry-put (point) "Toodledo-ID" (elt (car result) 2))
              (org-entry-put (point) "Sync"
                             (format "%d" (float-time (current-time)) 1000))))
        ;; Old task, update
        (when (org-toodledo-success-p (org-toodledo-edit-task task))
          (if (equal (org-toodledo-task-completed task) "1")
              (org-entry-put (point) "Completed" "1")
            (org-entry-put (point) "Status" (org-toodledo-task-status task)))
          (org-entry-put (point) "Sync"
                         (format "%d" (float-time (current-time)) 1000)))))))

;; (assert (equal (org-toodledo-format-date "2003-08-12") "<2003-08-12 Tue>"))
(defun org-toodledo-format-date (date &optional repeat)
  "Return yyyy-mm-dd day for DATE."
  (concat
   "<"
   (format-time-string
    "%Y-%m-%d %a"
    (cond
     ((listp date) date)
     ((numberp date) (seconds-to-time date))
     ((and (stringp date)
           (string-match "^[0-9]+$" date))
      (seconds-to-time (string-to-number date)))
     (t (apply 'encode-time (org-parse-time-string date)))))
   (if repeat (concat " " repeat) "")
   ">"))

;; (mapconcat 'org-toodledo-task-to-string temp "\n")
;; (setq task (elt temp 2))
;; (org-toodledo-task-to-string task)
(defun org-toodledo-task-to-string (task &optional level)
  "Return an Org-formatted version of TASK."
  (let* ((repeat (string-to-number (org-toodledo-task-repeat task)))
         (rep-advanced (org-toodledo-task-repeat-advanced task))
         (repeat-string (org-toodledo-repeat-to-string repeat rep-advanced))
         (priority (org-toodledo-task-priority task)))
    (concat
     (make-string (or level 2) ?*) " "
     (org-toodledo-status-to-string task) " "
     (cond
      ((equal priority "-1") "")
      ((equal priority "0") "[#C] ")
      ((equal priority "1") "[#B] ")
      ((equal priority "2") "[#A] ")
      ((equal priority "3") "[#A] "))
     (org-toodledo-task-title task)
     (if (org-toodledo-task-context task)
         (concat " :@" (org-toodledo-task-context task) ":") 
       "")
     "\n"
     (if (and (org-toodledo-task-duedate task)
              (not (equal (org-toodledo-task-duedate task) ""))
              (not (< (string-to-number (org-toodledo-task-duedate task)) 0)))
         (concat org-deadline-string " "
                 (org-toodledo-format-date
                  (org-toodledo-task-duedate task)
                  repeat-string)
                 "\n")
       "")
     (or (org-toodledo-task-note task) "") "\n"
     ":PROPERTIES:\n"
     ":Toodledo-ID: " (org-toodledo-task-id task) "\n"
     ":Modified: " (org-toodledo-task-modified task) "\n"
     ":Sync: " (format "%d" (float-time (current-time))) "\n"
     ":Effort: " (org-toodledo-task-length task) "\n"
     ":END:\n"
     )))

;; (assert (equal (org-toodledo-repeat-to-string 0) ""))
;; (assert (equal (org-toodledo-repeat-to-string 1) "+1w"))
;; (assert (equal (org-toodledo-repeat-to-string 2) "+1m"))
;; (assert (equal (org-toodledo-repeat-to-string 3) "+1y"))
;; (assert (equal (org-toodledo-repeat-to-string 4) "+1d"))
;; (assert (equal (org-toodledo-repeat-to-string 5) "+2w"))
;; (assert (equal (org-toodledo-repeat-to-string 6) "+2m"))
;; (assert (equal (org-toodledo-repeat-to-string 7) "+6m"))
;; (assert (equal (org-toodledo-repeat-to-string 8) "+3m"))
;; (assert (equal (org-toodledo-repeat-to-string 108) ".+3m"))
;; (assert (equal (org-toodledo-repeat-to-string 101) ".+1w"))
;; (assert (equal (org-toodledo-repeat-to-string 0) ""))

(defconst org-toodledo-repeat-intervals '("" "+1w" "+1m" "+1y" "+1d" "+2w" "+2m" "+6m" "+3m"))
(defun org-toodledo-status-to-string (task)
  (let ((comp (org-toodledo-task-completed task))
        (status (string-to-number (org-toodledo-task-status task))))
    (cond
     ((not (or (null comp) (equal comp "") (equal comp "0"))) "DONE")
     ((= status 0) "TODO")
     ((= status 1) "TODO")
     ((= status 2) "STARTED")
     ((= status 3) "PLAN")
     ((= status 4) "DELEGATED")
     ((= status 5) "WAITING")
     ((= status 6) "PLAN")  ; hold
     ((= status 7) "SOMEDAY")  ; postponed
     ((= status 8) "SOMEDAY")
     ((= status 9) "CANCELLED")
     )))

(defun org-toodledo-repeat-to-string (repeat &optional rep-advanced)
  "Turn TASK into a repeat sequence."
  (cond
   ((= repeat 0) nil)
   ((> repeat 100) (concat "+" (org-toodledo-repeat-to-string (mod repeat 100) rep-advanced)))
   ((and (= repeat 50) rep-advanced)
    (cond
     ((string-match "Every \\([0-9]+\\) week" rep-advanced)
      (concat "+" (match-string 1 rep-advanced) "w"))
     ((string-match "Every \\([0-9]+\\) month" rep-advanced)
      (concat "+" (match-string 1 rep-advanced) "m"))
     ((string-match "Every \\([0-9]+\\) year" rep-advanced)
      (concat "+" (match-string 1 rep-advanced) "y"))
     ((string-match "Every \\([0-9]+\\) day" rep-advanced)
      (concat "+" (match-string 1 rep-advanced) "d"))
     (t rep-advanced)))
   (t (elt org-toodledo-repeat-intervals repeat))))

(defun org-toodledo-delete-current-task ()
  "Delete the current task."
  (interactive)
  (org-back-to-heading t)
  (let ((task (org-toodledo-parse-current-task)))
    (and (> (length (org-toodledo-task-id task)) 0)
         (org-toodledo-success-p (org-toodledo-delete-task task)))
    (delete-region
     (point)
     (if (and (end-of-line)
              (re-search-forward org-complex-heading-regexp nil t))
         (match-beginning 0)
       (org-end-of-subtree t t)
       (point)))))

  
(defun org-toodledo-task-get-prop (task prop) (cdr (assoc prop task)))
(defmacro org-toodledo-task-prop-defun (field)
  `(defun ,(intern (concat "org-toodledo-task-" field)) (task)
     (cdr (assoc ,field task))))

(defun org-toodledo-success-p (result)
  "Return non-nil if RESULT indicates success."
  (eq (car (car result)) 'success))
        
(org-toodledo-task-prop-defun "id")
(org-toodledo-task-prop-defun "title")
(org-toodledo-task-prop-defun "status")
(org-toodledo-task-prop-defun "completed")
(org-toodledo-task-prop-defun "repeat")
(org-toodledo-task-prop-defun "context")
(org-toodledo-task-prop-defun "duedate")
(org-toodledo-task-prop-defun "modified")
(org-toodledo-task-prop-defun "priority")
(org-toodledo-task-prop-defun "note")
(org-toodledo-task-prop-defun "length")
;; defun'd separately because of the change in name
(defun org-toodledo-task-repeat-advanced (task)
  (cdr (assoc "rep_advanced" task)))

(defvar org-toodledo-contexts nil "An alist of (context . id).")
(defun org-toodledo-get-contexts (&optional force)
  "Store an alist of (context . id) in `org-toodledo-contexts'.
Reload if FORCE is non-nil."
  (if (or force (null org-toodledo-contexts))
      (setq org-toodledo-contexts
            (mapcar
             (lambda (node)
               (cons
              (car (xml-node-children node))
              (xml-get-attribute node 'id)))
             (xml-get-children (car
                                (org-toodledo-call-method "getContexts")) 'context)))
    org-toodledo-contexts))

(defun org-toodledo-agenda-touch ()
  "Update the Modified timestamp for the current entry in the agenda."
  (org-agenda-check-type t 'agenda 'timeline)
  (org-agenda-check-no-diary)
  (let* ((marker (or (org-get-at-bol 'org-marker)
                     (org-agenda-error)))
         (buffer (marker-buffer marker))
         (pos (marker-position marker)))
    (org-with-remote-undo buffer
     (with-current-buffer buffer
       (widen)
       (goto-char pos)
       (if (org-entry-get (point) "Modified")
           (org-entry-put (point) "Modified" (format "%d" (float-time (current-time)))))))))


(defun org-toodledo-update-task (task &optional last-update)
  (let* ((modified (string-to-number (or (org-entry-get (point) "Modified") "")))
         (last-sync (if (org-entry-get (point) "Sync")
                        (string-to-number (org-entry-get (point) "Sync"))
                      0))
         (level (car (org-heading-components)))
         (locally-modified (> modified last-sync)))
    ;; Locally modified? keep
    (if locally-modified
        nil
      ;; Not locally modified? replace
      ;; Figure out what our level is
      (delete-region (org-back-to-heading)
                     (progn (goto-char (match-end 0))
                            (if (re-search-forward org-complex-heading-regexp nil t)
                                (goto-char (match-beginning 0))
                              (org-end-of-subtree))))
      (insert (org-toodledo-task-to-string task level))
      t)))

(provide 'org-toodledo)
Short URL: http://sachachua.com/blog/p/7192

Picking hobbies that fit together

Some people have one hobby at a time. I tend to have several that shift over time, and I’ve gone through a lot of interests.

I started sketching my current hobbies to get a sense of how woodworking might fit in, and how it interacts with my other interests. I wanted to figure out if I could explore that interest enough to reach the point of being able to comfortably do it. In particular, woodworking tends to require blocks of unstructured time, which means it competes with sewing and cooking for those precious weekend blocks.

hobbies

I got interested in woodworking  because of gardening, and there are a number of items we can build to make gardening easier. Now that we’ve set the garden up, it doesn’t take that much more time: ten minutes each day, and maybe an hour during weekends to turn the compost and take care of additional tasks.

Woodworking conflicts with cooking, though, because we use the kitchen and there’s no point in cooking when sawdust is floating around. That’s okay. We’re at a “good enough” level in cooking, and batch-cooking lets us free up weekends.

Drawing helps a lot with woodworking, and woodworking helps me develop spatial intelligence for better drawing.

Looking at my other hobbies, you can see that writing, drawing, and presentations all feed each other. I’m not making as good a use of photography as I could, so that’s something to develop. All the hobbies that I actively work on are well-connected. In contrast, something like music doesn’t connect well with my other interests, so I rarely end up practising on the piano.

Picking hobbies that fit together means that you get more value for the time and energy you put in. The more you develop skills in one area, the more effectively you can do connected areas.

Another related post: How to do a lot

Short URL: http://sachachua.com/blog/p/7188

Meeting resolutions

I want to participate in and facilitate better meetings. Here are some resolutions I will strive towards:

  • I will make sure all my meetings have clear objectives and agendas.
  • I will limit meeting invitation to people who are necessary, and I will explain their involvement.
  • I will follow up with action items that name specific people with responsibilities.
  • I will make sure meeting invites include call-in numbers for all the countries expected.
  • I will make sure meeting invites include web conference instructions if needed.
  • I will not use the mute button.
  • I will not multitask.
  • I will schedule meetings so that people have enough time to transition from their previous meeting and to their next meeting. This probably means starting five minutes after the hour, and ending in 20 minutes or 45/50 minutes.
  • I will insist that participants do not take calls from their cars, and will reschedule if necessary.
  • I will schedule meetings at least two days in advance, to give people time to respond.
  • I will send materials at least one day in advance.

Looking forward to adding to this list as I learn more!

Short URL: http://sachachua.com/blog/p/6912

Get the highlights as a PDF!

Stories from my Twenties: Highlights from a Decade of Blogging

Free sample!