org-mac-iCal.el 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. ;;; org-mac-iCal.el --- Imports events from iCal.app to the Emacs diary
  2. ;; Copyright (C) 2009 Christopher Suckling
  3. ;; Author: Christopher Suckling <suckling at gmail dot com>
  4. ;; This file is Free Software; you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation; either version 3, or (at your option)
  7. ;; any later version.
  8. ;; It is distributed in the hope that it will be useful, but WITHOUT
  9. ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  10. ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
  11. ;; License for more details.
  12. ;; You should have received a copy of the GNU General Public License
  13. ;; along with GNU Emacs; see the file COPYING. If not, write to the
  14. ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  15. ;; Boston, MA 02110-1301, USA.
  16. ;; Version: 0.1057.104
  17. ;; Keywords: outlines, calendar
  18. ;;; Commentary:
  19. ;;
  20. ;; This file provides the import of events from Mac OS X 10.5 iCal.app
  21. ;; into the Emacs diary (it is not compatible with OS X < 10.5). The
  22. ;; function org-mac-iCal will import events in all checked iCal.app
  23. ;; calendars for the date range org-mac-iCal-range months, centered
  24. ;; around the current date.
  25. ;;
  26. ;; CAVEAT: This function is destructive; it will overwrite the current
  27. ;; contents of the Emacs diary.
  28. ;;
  29. ;; Installation: add (require 'org-mac-iCal) to your .emacs.
  30. ;;
  31. ;; If you view Emacs diary entries in org-agenda, the following hook
  32. ;; will ensure that all-day events are not orphaned below TODO items
  33. ;; and that any supplementary fields to events (e.g. Location) are
  34. ;; grouped with their parent event
  35. ;;
  36. ;; (add-hook 'org-agenda-cleanup-fancy-diary-hook
  37. ;; (lambda ()
  38. ;; (goto-char (point-min))
  39. ;; (save-excursion
  40. ;; (while (re-search-forward "^[a-z]" nil t)
  41. ;; (goto-char (match-beginning 0))
  42. ;; (insert "0:00-24:00 ")))
  43. ;; (while (re-search-forward "^ [a-z]" nil t)
  44. ;; (goto-char (match-beginning 0))
  45. ;; (save-excursion
  46. ;; (re-search-backward "^[0-9]+:[0-9]+-[0-9]+:[0-9]+ " nil t))
  47. ;; (insert (match-string 0)))))
  48. ;;; Code:
  49. (defcustom org-mac-iCal-range 2
  50. "The range in months to import iCal.app entries into the Emacs
  51. diary. The import is centered around today's date; thus a value
  52. of 2 imports entries for one month before and one month after
  53. today's date"
  54. :group 'org-time
  55. :type 'integer)
  56. (defun org-mac-iCal ()
  57. "Selects checked calendars in iCal.app and imports them into
  58. the the Emacs diary"
  59. (interactive)
  60. ;; kill diary buffers then empty diary files to avoid duplicates
  61. (setq currentBuffer (buffer-name))
  62. (setq openBuffers (mapcar (function buffer-name) (buffer-list)))
  63. (omi-kill-diary-buffer openBuffers)
  64. (with-temp-buffer
  65. (insert-file-contents diary-file)
  66. (delete-region (point-min) (point-max))
  67. (write-region (point-min) (point-max) diary-file))
  68. ;; determine available calendars
  69. (setq caldav-folders (directory-files "~/Library/Calendars" 1 ".*caldav$"))
  70. (setq caldav-calendars nil)
  71. (mapc
  72. (lambda (x)
  73. (setq caldav-calendars (nconc caldav-calendars (directory-files x 1 ".*calendar$"))))
  74. caldav-folders)
  75. (setq local-calendars nil)
  76. (setq local-calendars (directory-files "~/Library/Calendars" 1 ".*calendar$"))
  77. (setq all-calendars (append caldav-calendars local-calendars))
  78. ;; parse each calendar's Info.plist to see if calendar is checked in iCal
  79. (setq all-calendars (delq 'nil (mapcar
  80. (lambda (x)
  81. (omi-checked x))
  82. all-calendars)))
  83. ;; for each caledar, concatenate individual events into a single ics file
  84. (with-temp-buffer
  85. (shell-command "sw_vers" " *temp*")
  86. (when (re-search-backward "10.5" nil t)
  87. (omi-concat-leopard-ics all-calendars)))
  88. ;; move any caldav ics files to the same place as local ics files
  89. (mapc
  90. (lambda (x)
  91. (when (directory-files x 1 ".*ics$")
  92. (rename-file (car (directory-files x 1 ".*ics$")) (concat "~/Library/Calendars/" (car (directory-files x nil ".*ics$"))))))
  93. caldav-folders)
  94. ;; check calendar has contents and import
  95. (setq import-calendars (directory-files "~/Library/Calendars" 1 ".*ics$"))
  96. (mapc
  97. (lambda (x)
  98. (when (/= (nth 7 (file-attributes x 'string)) 0)
  99. (omi-import-ics x)))
  100. import-calendars)
  101. ;; tidy up intermediate files and buffers
  102. (setq usedCalendarsBuffers (mapcar (function buffer-name) (buffer-list)))
  103. (omi-kill-ics-buffer usedCalendarsBuffers)
  104. (setq usedCalendarsFiles (directory-files "~/Library/Calendars" 1 ".*ics$"))
  105. (omi-delete-ics-file usedCalendarsFiles)
  106. (switch-to-buffer currentBuffer))
  107. (defun omi-concat-leopard-ics (list)
  108. "Leopard stores each iCal.app event in a separate ics file.
  109. Whilst useful for Spotlight indexing, this is less helpful for
  110. icalendar-import-file. omi-concat-leopard-ics concatenates these
  111. individual event files into a single ics file"
  112. (mapc
  113. (lambda (x)
  114. (setq omi-leopard-events (directory-files (concat x "/Events") 1 ".*ics$"))
  115. (with-temp-buffer
  116. (mapc
  117. (lambda (y)
  118. (insert-file-contents (expand-file-name y)))
  119. omi-leopard-events)
  120. (write-region (point-min) (point-max) (concat (expand-file-name x) ".ics"))))
  121. list))
  122. (defun omi-import-ics (string)
  123. "Imports an ics file into the Emacs diary. First tidies up the
  124. ics file so that it is suitable for import and selects a sensible
  125. date range so that Emacs calendar view doesn't grind to a halt"
  126. (with-temp-buffer
  127. (insert-file-contents string)
  128. (goto-char (point-min))
  129. (while
  130. (re-search-forward "^BEGIN:VCALENDAR$" nil t)
  131. (setq startEntry (match-beginning 0))
  132. (re-search-forward "^END:VCALENDAR$" nil t)
  133. (setq endEntry (match-end 0))
  134. (save-restriction
  135. (narrow-to-region startEntry endEntry)
  136. (goto-char (point-min))
  137. (re-search-forward "\\(^DTSTART;.*:\\)\\([0-9][0-9][0-9][0-9]\\)\\([0-9][0-9]\\)" nil t)
  138. (if (or (eq (match-string 2) nil) (eq (match-string 3) nil))
  139. (progn
  140. (setq yearEntry 0)
  141. (setq monthEntry 0))
  142. (setq yearEntry (string-to-number (match-string 2)))
  143. (setq monthEntry (string-to-number (match-string 3))))
  144. (setq year (string-to-number (format-time-string "%Y")))
  145. (setq month (string-to-number (format-time-string "%m")))
  146. (when (or
  147. (and
  148. (= yearEntry year)
  149. (or (< monthEntry (- month (/ org-mac-iCal-range 2))) (> monthEntry (+ month (/ org-mac-iCal-range 2)))))
  150. (< yearEntry (- year 1))
  151. (> yearEntry (+ year 1))
  152. (and
  153. (= yearEntry (- year 1)) (/= monthEntry 12))
  154. (and
  155. (= yearEntry (+ year 1)) (/= monthEntry 1)))
  156. (delete-region startEntry endEntry))))
  157. (while
  158. (re-search-forward "^END:VEVENT$" nil t)
  159. (delete-blank-lines))
  160. (goto-line 1)
  161. (insert "BEGIN:VCALENDAR\n\n")
  162. (goto-line 2)
  163. (while
  164. (re-search-forward "^BEGIN:VCALENDAR$" nil t)
  165. (replace-match "\n"))
  166. (goto-line 2)
  167. (while
  168. (re-search-forward "^END:VCALENDAR$" nil t)
  169. (replace-match "\n"))
  170. (insert "END:VCALENDAR")
  171. (goto-line 1)
  172. (delete-blank-lines)
  173. (while
  174. (re-search-forward "^END:VEVENT$" nil t)
  175. (delete-blank-lines))
  176. (goto-line 1)
  177. (while
  178. (re-search-forward "^ORG.*" nil t)
  179. (replace-match "\n"))
  180. (goto-line 1)
  181. (write-region (point-min) (point-max) string))
  182. (icalendar-import-file string diary-file))
  183. (defun omi-kill-diary-buffer (list)
  184. (mapc
  185. (lambda (x)
  186. (if (string-match "^diary" x)
  187. (kill-buffer x)))
  188. list))
  189. (defun omi-kill-ics-buffer (list)
  190. (mapc
  191. (lambda (x)
  192. (if (string-match "ics$" x)
  193. (kill-buffer x)))
  194. list))
  195. (defun omi-delete-ics-file (list)
  196. (mapc
  197. (lambda (x)
  198. (delete-file x))
  199. list))
  200. (defun omi-checked (directory)
  201. "Parse Info.plist in iCal.app calendar folder and determine
  202. whether Checked key is 1. If Checked key is not 1, remove
  203. calendar from list of calendars for import"
  204. (let* ((root (xml-parse-file (car (directory-files directory 1 "Info.plist"))))
  205. (plist (car root))
  206. (dict (car (xml-get-children plist 'dict)))
  207. (keys (cdr (xml-node-children dict)))
  208. (keys (mapcar
  209. (lambda (x)
  210. (cond ((listp x)
  211. x)))
  212. keys))
  213. (keys (delq 'nil keys)))
  214. (when (equal "1" (car (cddr (lax-plist-get keys '(key nil "Checked")))))
  215. directory)))
  216. (provide 'org-mac-iCal)
  217. ;;; org-mac-iCal.el ends here