* ps-print.el (ps-face-attribute-list): Handle anonymous faces
[bpt/emacs.git] / lisp / org / org-mobile.el
CommitLineData
8d642074 1;;; org-mobile.el --- Code for asymmetric sync with a mobile device
ab422c4d 2;; Copyright (C) 2009-2013 Free Software Foundation, Inc.
8d642074
CD
3;;
4;; Author: Carsten Dominik <carsten at orgmode dot org>
5;; Keywords: outlines, hypermedia, calendar, wp
6;; Homepage: http://orgmode.org
8d642074
CD
7;;
8;; This file is part of GNU Emacs.
9;;
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14;;
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19;;
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22;;
23;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
24;;
25;;; Commentary:
26;;
27;; This file contains the code to interact with Richard Moreland's iPhone
afe98dfa
CD
28;; application MobileOrg, as well as with the Android version by Matthew Jones.
29;; This code is documented in Appendix B of the Org-mode manual. The code is
30;; not specific for the iPhone and Android - any external
31;; viewer/flagging/editing application that uses the same conventions could
32;; be used.
8d642074
CD
33
34(require 'org)
35(require 'org-agenda)
86fbb8ca
CD
36;;; Code:
37
8bfe682a 38(eval-when-compile (require 'cl))
8d642074 39
14e1337f 40(declare-function org-pop-to-buffer-same-window
e66ba1df
BG
41 "org-compat" (&optional buffer-or-name norecord label))
42
8d642074 43(defgroup org-mobile nil
8bfe682a 44 "Options concerning support for a viewer/editor on a mobile device."
8d642074
CD
45 :tag "Org Mobile"
46 :group 'org)
47
48(defcustom org-mobile-files '(org-agenda-files)
49 "Files to be staged for MobileOrg.
8bfe682a 50This is basically a list of files and directories. Files will be staged
8d642074
CD
51directly. Directories will be search for files with the extension `.org'.
52In addition to this, the list may also contain the following symbols:
53
54org-agenda-files
ed21c5c8 55 This means include the complete, unrestricted list of files given in
8d642074
CD
56 the variable `org-agenda-files'.
57org-agenda-text-search-extra-files
58 Include the files given in the variable
59 `org-agenda-text-search-extra-files'"
60 :group 'org-mobile
61 :type '(list :greedy t
62 (option (const :tag "org-agenda-files" org-agenda-files))
63 (option (const :tag "org-agenda-text-search-extra-files"
64 org-agenda-text-search-extra-files))
65 (repeat :inline t :tag "Additional files"
66 (file))))
67
3ab2c837
BG
68(defcustom org-mobile-files-exclude-regexp ""
69 "A regexp to exclude files from `org-mobile-files'."
70 :group 'org-mobile
372d7b21 71 :version "24.1"
3ab2c837
BG
72 :type 'regexp)
73
8d642074
CD
74(defcustom org-mobile-directory ""
75 "The WebDAV directory where the interaction with the mobile takes place."
76 :group 'org-mobile
77 :type 'directory)
78
ed21c5c8 79(defcustom org-mobile-use-encryption nil
86fbb8ca 80 "Non-nil means keep only encrypted files on the WebDAV server.
ed21c5c8
CD
81Encryption uses AES-256, with a password given in
82`org-mobile-encryption-password'.
83When nil, plain files are kept on the server.
84Turning on encryption requires to set the same password in the MobileOrg
86fbb8ca
CD
85application. Before turning this on, check of MobileOrg does already
86support it - at the time of this writing it did not yet."
ed21c5c8 87 :group 'org-mobile
372d7b21 88 :version "24.1"
ed21c5c8
CD
89 :type 'boolean)
90
91(defcustom org-mobile-encryption-tempfile "~/orgtmpcrypt"
92 "File that is being used as a temporary file for encryption.
86fbb8ca 93This must be local file on your local machine (not on the WebDAV server).
ed21c5c8
CD
94You might want to put this file into a directory where only you have access."
95 :group 'org-mobile
372d7b21 96 :version "24.1"
ed21c5c8
CD
97 :type 'directory)
98
99(defcustom org-mobile-encryption-password ""
100 "Password for encrypting files uploaded to the server.
101This is a single password which is used for AES-256 encryption. The same
102password must also be set in the MobileOrg application. All Org files,
103including mobileorg.org will be encrypted using this password.
afe98dfa
CD
104
105SECURITY CONSIDERATIONS:
106
86fbb8ca 107Note that, when Org runs the encryption commands, the password could
afe98dfa
CD
108be visible briefly on your system with the `ps' command. So this method is
109only intended to keep the files secure on the server, not on your own machine.
110
111Also, if you set this variable in an init file (.emacs or .emacs.d/init.el
112or custom.el...) and if that file is stored in a way so that other can read
113it, this also limits the security of this approach. You can also leave
114this variable empty - Org will then ask for the password once per Emacs
115session."
ed21c5c8 116 :group 'org-mobile
372d7b21 117 :version "24.1"
ed21c5c8
CD
118 :type '(string :tag "Password"))
119
afe98dfa
CD
120(defvar org-mobile-encryption-password-session nil)
121
122(defun org-mobile-encryption-password ()
123 (or (org-string-nw-p org-mobile-encryption-password)
124 (org-string-nw-p org-mobile-encryption-password-session)
125 (setq org-mobile-encryption-password-session
126 (read-passwd "Password for MobileOrg: " t))))
127
8d642074
CD
128(defcustom org-mobile-inbox-for-pull "~/org/from-mobile.org"
129 "The file where captured notes and flags will be appended to.
130During the execution of `org-mobile-pull', the file
131`org-mobile-capture-file' will be emptied it's contents have
8bfe682a
CD
132been appended to the file given here. This file should be in
133`org-directory', and not in the staging area or on the web server."
8d642074
CD
134 :group 'org-mobile
135 :type 'file)
136
137(defconst org-mobile-capture-file "mobileorg.org"
138 "The capture file where the mobile stores captured notes and flags.
139This should not be changed, because MobileOrg assumes this name.")
140
141(defcustom org-mobile-index-file "index.org"
3ab2c837 142 "The index file with links to all Org files that should be loaded by MobileOrg.
8d642074
CD
143Relative to `org-mobile-directory'. The Address field in the MobileOrg setup
144should point to this file."
145 :group 'org-mobile
146 :type 'file)
147
ed21c5c8
CD
148(defcustom org-mobile-agendas 'all
149 "The agendas that should be pushed to MobileOrg.
150Allowed values:
151
152default the weekly agenda and the global TODO list
153custom all custom agendas defined by the user
154all the custom agendas and the default ones
155list a list of selection key(s) as string."
156 :group 'org-mobile
372d7b21 157 :version "24.1"
ed21c5c8
CD
158 :type '(choice
159 (const :tag "Default Agendas" default)
160 (const :tag "Custom Agendas" custom)
161 (const :tag "Default and Custom Agendas" all)
162 (repeat :tag "Selected"
163 (string :tag "Selection Keys"))))
164
8d642074 165(defcustom org-mobile-force-id-on-agenda-items t
afe98dfa 166 "Non-nil means make all agenda items carry an ID."
8d642074
CD
167 :group 'org-mobile
168 :type 'boolean)
169
8bfe682a 170(defcustom org-mobile-force-mobile-change nil
ed21c5c8 171 "Non-nil means force the change made on the mobile device.
8bfe682a
CD
172So even if there have been changes to the computer version of the entry,
173force the new value set on the mobile.
174When nil, mark the entry from the mobile with an error message.
175Instead of nil or t, this variable can also be a list of symbols, indicating
176the editing types for which the mobile version should always dominate."
177 :group 'org-mobile
178 :type '(choice
179 (const :tag "Always" t)
180 (const :tag "Never" nil)
181 (set :greedy t :tag "Specify"
182 (const todo)
183 (const tags)
184 (const priority)
185 (const heading)
186 (const body))))
187
8d642074 188(defcustom org-mobile-action-alist
8bfe682a 189 '(("edit" . (org-mobile-edit data old new)))
8d642074
CD
190 "Alist with flags and actions for mobile sync.
191When flagging an entry, MobileOrg will create entries that look like
192
193 * F(action:data) [[id:entry-id][entry title]]
194
195This alist defines that the ACTION in the parentheses of F() should mean,
196i.e. what action should be taken. The :data part in the parenthesis is
197optional. If present, the string after the colon will be passed to the
198action form as the `data' variable.
199The car of each elements of the alist is an actions string. The cdr is
200an Emacs Lisp form that will be evaluated with the cursor on the headline
8bfe682a
CD
201of that entry.
202
203For now, it is not recommended to change this variable."
8d642074
CD
204 :group 'org-mobile
205 :type '(repeat
206 (cons (string :tag "Action flag")
207 (sexp :tag "Action form"))))
208
8bfe682a
CD
209(defcustom org-mobile-checksum-binary (or (executable-find "shasum")
210 (executable-find "sha1sum")
211 (executable-find "md5sum")
212 (executable-find "md5"))
213 "Executable used for computing checksums of agenda files."
214 :group 'org-mobile
215 :type 'string)
216
8d642074
CD
217(defvar org-mobile-pre-push-hook nil
218 "Hook run before running `org-mobile-push'.
219This could be used to clean up `org-mobile-directory', for example to
220remove files that used to be included in the agenda but no longer are.
221The presence of such files would not really be a problem, but after time
222they may accumulate.")
223
224(defvar org-mobile-post-push-hook nil
225 "Hook run after running `org-mobile-push'.
226If Emacs does not have direct write access to the WebDAV directory used
227by the mobile device, this hook should be used to copy all files from the
228local staging directory `org-mobile-directory' to the WebDAV directory,
229for example using `rsync' or `scp'.")
230
231(defvar org-mobile-pre-pull-hook nil
232 "Hook run before executing `org-mobile-pull'.
233If Emacs does not have direct write access to the WebDAV directory used
234by the mobile device, this hook should be used to copy the capture file
235`mobileorg.org' from the WebDAV location to the local staging
236directory `org-mobile-directory'.")
237
238(defvar org-mobile-post-pull-hook nil
8223b1d2 239 "Hook run after running `org-mobile-pull', only if new items were found.
8d642074
CD
240If Emacs does not have direct write access to the WebDAV directory used
241by the mobile device, this hook should be used to copy the emptied
242capture file `mobileorg.org' back to the WebDAV directory, for example
243using `rsync' or `scp'.")
244
245(defvar org-mobile-last-flagged-files nil
8bfe682a 246 "List of files containing entries flagged in the latest pull.")
8d642074
CD
247
248(defvar org-mobile-files-alist nil)
249(defvar org-mobile-checksum-files nil)
250
251(defun org-mobile-prepare-file-lists ()
252 (setq org-mobile-files-alist (org-mobile-files-alist))
8bfe682a 253 (setq org-mobile-checksum-files nil))
8d642074
CD
254
255(defun org-mobile-files-alist ()
3ab2c837
BG
256 "Expand the list in `org-mobile-files' to a list of existing files.
257Also exclude files matching `org-mobile-files-exclude-regexp'."
8bfe682a
CD
258 (let* ((include-archives
259 (and (member 'org-agenda-text-search-extra-files org-mobile-files)
260 (member 'agenda-archives org-agenda-text-search-extra-files)
261 t))
262 (files
263 (apply 'append
264 (mapcar
265 (lambda (f)
266 (cond
267 ((eq f 'org-agenda-files)
268 (org-agenda-files t include-archives))
269 ((eq f 'org-agenda-text-search-extra-files)
270 (delq 'agenda-archives
271 (copy-sequence
272 org-agenda-text-search-extra-files)))
273 ((and (stringp f) (file-directory-p f))
274 (directory-files f 'full "\\.org\\'"))
275 ((and (stringp f) (file-exists-p f))
276 (list f))
277 (t nil)))
278 org-mobile-files)))
3ab2c837 279 (files (delete
14e1337f 280 nil
3ab2c837
BG
281 (mapcar (lambda (f)
282 (unless (and (not (string= org-mobile-files-exclude-regexp ""))
283 (string-match org-mobile-files-exclude-regexp f))
284 (identity f)))
285 files)))
8d642074
CD
286 (orgdir-uname (file-name-as-directory (file-truename org-directory)))
287 (orgdir-re (concat "\\`" (regexp-quote orgdir-uname)))
288 uname seen rtn file link-name)
289 ;; Make the files unique, and determine the name under which they will
290 ;; be listed.
291 (while (setq file (pop files))
8bfe682a
CD
292 (if (not (file-name-absolute-p file))
293 (setq file (expand-file-name file org-directory)))
8d642074
CD
294 (setq uname (file-truename file))
295 (unless (member uname seen)
296 (push uname seen)
297 (if (string-match orgdir-re uname)
298 (setq link-name (substring uname (match-end 0)))
299 (setq link-name (file-name-nondirectory uname)))
300 (push (cons file link-name) rtn)))
301 (nreverse rtn)))
302
8223b1d2
BG
303(defvar org-agenda-filter)
304
8d642074
CD
305;;;###autoload
306(defun org-mobile-push ()
c7cf0ebc 307 "Push the current state of Org affairs to the target directory.
8d642074
CD
308This will create the index file, copy all agenda files there, and also
309create all custom agenda views, for upload to the mobile phone."
310 (interactive)
8bfe682a 311 (let ((a-buffer (get-buffer org-agenda-buffer-name)))
c7cf0ebc
BG
312 (let ((org-agenda-curbuf-name org-agenda-buffer-name)
313 (org-agenda-buffer-name "*SUMO*")
c74587e6 314 (org-agenda-tag-filter org-agenda-tag-filter)
8bfe682a
CD
315 (org-agenda-redo-command org-agenda-redo-command))
316 (save-excursion
317 (save-window-excursion
3ab2c837 318 (run-hooks 'org-mobile-pre-push-hook)
8bfe682a
CD
319 (org-mobile-check-setup)
320 (org-mobile-prepare-file-lists)
8bfe682a 321 (message "Creating agendas...")
8223b1d2
BG
322 (let ((inhibit-redisplay t)
323 (org-agenda-files (mapcar 'car org-mobile-files-alist)))
324 (org-mobile-create-sumo-agenda))
8bfe682a
CD
325 (message "Creating agendas...done")
326 (org-save-all-org-buffers) ; to save any IDs created by this process
327 (message "Copying files...")
328 (org-mobile-copy-agenda-files)
329 (message "Writing index file...")
330 (org-mobile-create-index-file)
331 (message "Writing checksums...")
332 (org-mobile-write-checksums)
c7cf0ebc
BG
333 (run-hooks 'org-mobile-post-push-hook)))
334 (setq org-agenda-buffer-name org-agenda-curbuf-name
335 org-agenda-this-buffer-name org-agenda-curbuf-name))
8bfe682a 336 (redraw-display)
c7cf0ebc 337 (when (buffer-live-p a-buffer)
8bfe682a 338 (if (not (get-buffer-window a-buffer))
c7cf0ebc
BG
339 (kill-buffer a-buffer)
340 (let ((cw (selected-window)))
341 (select-window (get-buffer-window a-buffer))
342 (org-agenda-redo)
343 (select-window cw)))))
8d642074 344 (message "Files for mobile viewer staged"))
ed21c5c8 345
8bfe682a
CD
346(defvar org-mobile-before-process-capture-hook nil
347 "Hook that is run after content was moved to `org-mobile-inbox-for-pull'.
ed21c5c8
CD
348The inbox file is visited by the current buffer, and the buffer is
349narrowed to the newly captured data.")
8d642074
CD
350
351;;;###autoload
352(defun org-mobile-pull ()
353 "Pull the contents of `org-mobile-capture-file' and integrate them.
354Apply all flagged actions, flag entries to be flagged and then call an
355agenda view showing the flagged items."
356 (interactive)
357 (org-mobile-check-setup)
358 (run-hooks 'org-mobile-pre-pull-hook)
359 (let ((insertion-marker (org-mobile-move-capture)))
360 (if (not (markerp insertion-marker))
361 (message "No new items")
362 (org-with-point-at insertion-marker
8bfe682a
CD
363 (save-restriction
364 (narrow-to-region (point) (point-max))
365 (run-hooks 'org-mobile-before-process-capture-hook)))
366 (org-with-point-at insertion-marker
367 (org-mobile-apply (point) (point-max)))
8d642074
CD
368 (move-marker insertion-marker nil)
369 (run-hooks 'org-mobile-post-pull-hook)
370 (when org-mobile-last-flagged-files
371 ;; Make an agenda view of flagged entries, but only in the files
372 ;; where stuff has been added.
373 (put 'org-agenda-files 'org-restrict org-mobile-last-flagged-files)
8bfe682a 374 (let ((org-agenda-keep-restricted-file-list t))
8d642074
CD
375 (org-agenda nil "?"))))))
376
377(defun org-mobile-check-setup ()
378 "Check if org-mobile-directory has been set up."
afe98dfa 379 (org-mobile-cleanup-encryption-tempfile)
8bfe682a
CD
380 (unless (and org-directory
381 (stringp org-directory)
382 (string-match "\\S-" org-directory)
383 (file-exists-p org-directory)
384 (file-directory-p org-directory))
385 (error
386 "Please set `org-directory' to the directory where your org files live"))
387 (unless (and org-mobile-directory
388 (stringp org-mobile-directory)
389 (string-match "\\S-" org-mobile-directory)
390 (file-exists-p org-mobile-directory)
391 (file-directory-p org-mobile-directory))
8d642074
CD
392 (error
393 "Variable `org-mobile-directory' must point to an existing directory"))
8bfe682a
CD
394 (unless (and org-mobile-inbox-for-pull
395 (stringp org-mobile-inbox-for-pull)
396 (string-match "\\S-" org-mobile-inbox-for-pull)
397 (file-exists-p
398 (file-name-directory org-mobile-inbox-for-pull)))
8d642074 399 (error
ed21c5c8 400 "Variable `org-mobile-inbox-for-pull' must point to a file in an existing directory"))
86fbb8ca
CD
401 (unless (and org-mobile-checksum-binary
402 (string-match "\\S-" org-mobile-checksum-binary))
403 (error "No executable found to compute checksums"))
ed21c5c8 404 (when org-mobile-use-encryption
afe98dfa 405 (unless (string-match "\\S-" (org-mobile-encryption-password))
ed21c5c8
CD
406 (error
407 "To use encryption, you must set `org-mobile-encryption-password'"))
408 (unless (file-writable-p org-mobile-encryption-tempfile)
86fbb8ca 409 (error "Cannot write to encryption tempfile %s"
ed21c5c8
CD
410 org-mobile-encryption-tempfile))
411 (unless (executable-find "openssl")
8223b1d2 412 (error "OpenSSL is needed to encrypt files"))))
8d642074
CD
413
414(defun org-mobile-create-index-file ()
415 "Write the index file in the WebDAV directory."
8bfe682a
CD
416 (let ((files-alist (sort (copy-sequence org-mobile-files-alist)
417 (lambda (a b) (string< (cdr a) (cdr b)))))
418 (def-todo (default-value 'org-todo-keywords))
419 (def-tags (default-value 'org-tag-alist))
afe98dfa
CD
420 (target-file (expand-file-name org-mobile-index-file
421 org-mobile-directory))
8bfe682a 422 file link-name todo-kwds done-kwds tags drawers entry kwds dwds twds)
c7cf0ebc
BG
423 (when (stringp (car def-todo))
424 (setq def-todo (list (cons 'sequence def-todo))))
8223b1d2 425 (org-agenda-prepare-buffers (mapcar 'car files-alist))
8d642074
CD
426 (setq done-kwds (org-uniquify org-done-keywords-for-agenda))
427 (setq todo-kwds (org-delete-all
428 done-kwds
429 (org-uniquify org-todo-keywords-for-agenda)))
430 (setq drawers (org-uniquify org-drawers-for-agenda))
8223b1d2
BG
431 (setq tags (mapcar 'car (org-global-tags-completion-table
432 (mapcar 'car files-alist))))
8d642074 433 (with-temp-file
afe98dfa
CD
434 (if org-mobile-use-encryption
435 org-mobile-encryption-tempfile
436 target-file)
8bfe682a
CD
437 (while (setq entry (pop def-todo))
438 (insert "#+READONLY\n")
439 (setq kwds (mapcar (lambda (x) (if (string-match "(" x)
440 (substring x 0 (match-beginning 0))
441 x))
442 (cdr entry)))
443 (insert "#+TODO: " (mapconcat 'identity kwds " ") "\n")
444 (setq dwds (member "|" kwds)
445 twds (org-delete-all dwds kwds)
446 todo-kwds (org-delete-all twds todo-kwds)
447 done-kwds (org-delete-all dwds done-kwds)))
448 (when (or todo-kwds done-kwds)
449 (insert "#+TODO: " (mapconcat 'identity todo-kwds " ") " | "
450 (mapconcat 'identity done-kwds " ") "\n"))
451 (setq def-tags (mapcar
452 (lambda (x)
453 (cond ((null x) nil)
454 ((stringp x) x)
455 ((eq (car x) :startgroup) "{")
456 ((eq (car x) :endgroup) "}")
457 ((eq (car x) :newline) nil)
8223b1d2 458 ((listp x) (car x))))
8bfe682a
CD
459 def-tags))
460 (setq def-tags (delq nil def-tags))
461 (setq tags (org-delete-all def-tags tags))
462 (setq tags (sort tags (lambda (a b) (string< (downcase a) (downcase b)))))
463 (setq tags (append def-tags tags nil))
464 (insert "#+TAGS: " (mapconcat 'identity tags " ") "\n")
465 (insert "#+DRAWERS: " (mapconcat 'identity drawers " ") "\n")
466 (insert "#+ALLPRIORITIES: A B C" "\n")
467 (when (file-exists-p (expand-file-name
468 org-mobile-directory "agendas.org"))
469 (insert "* [[file:agendas.org][Agenda Views]]\n"))
8d642074
CD
470 (while (setq entry (pop files-alist))
471 (setq file (car entry)
472 link-name (cdr entry))
473 (insert (format "* [[file:%s][%s]]\n"
474 link-name link-name)))
8bfe682a 475 (push (cons org-mobile-index-file (md5 (buffer-string)))
afe98dfa
CD
476 org-mobile-checksum-files))
477 (when org-mobile-use-encryption
478 (org-mobile-encrypt-and-move org-mobile-encryption-tempfile
479 target-file)
480 (org-mobile-cleanup-encryption-tempfile))))
8d642074
CD
481
482(defun org-mobile-copy-agenda-files ()
483 "Copy all agenda files to the stage or WebDAV directory."
484 (let ((files-alist org-mobile-files-alist)
8bfe682a 485 file buf entry link-name target-path target-dir check)
8d642074
CD
486 (while (setq entry (pop files-alist))
487 (setq file (car entry) link-name (cdr entry))
488 (when (file-exists-p file)
489 (setq target-path (expand-file-name link-name org-mobile-directory)
490 target-dir (file-name-directory target-path))
491 (unless (file-directory-p target-dir)
8bfe682a 492 (make-directory target-dir 'parents))
ed21c5c8
CD
493 (if org-mobile-use-encryption
494 (org-mobile-encrypt-and-move file target-path)
495 (copy-file file target-path 'ok-if-exists))
8bfe682a
CD
496 (setq check (shell-command-to-string
497 (concat org-mobile-checksum-binary " "
498 (shell-quote-argument (expand-file-name file)))))
499 (when (string-match "[a-fA-F0-9]\\{30,40\\}" check)
500 (push (cons link-name (match-string 0 check))
501 org-mobile-checksum-files))))
afe98dfa 502
8d642074
CD
503 (setq file (expand-file-name org-mobile-capture-file
504 org-mobile-directory))
8bfe682a
CD
505 (save-excursion
506 (setq buf (find-file file))
14e1337f 507 (when (and (= (point-min) (point-max)))
afe98dfa
CD
508 (insert "\n")
509 (save-buffer)
510 (when org-mobile-use-encryption
511 (write-file org-mobile-encryption-tempfile)
512 (org-mobile-encrypt-and-move org-mobile-encryption-tempfile file)))
8bfe682a
CD
513 (push (cons org-mobile-capture-file (md5 (buffer-string)))
514 org-mobile-checksum-files))
afe98dfa 515 (org-mobile-cleanup-encryption-tempfile)
8bfe682a 516 (kill-buffer buf)))
8d642074
CD
517
518(defun org-mobile-write-checksums ()
519 "Create checksums for all files in `org-mobile-directory'.
520The table of checksums is written to the file mobile-checksums."
8bfe682a
CD
521 (let ((sumfile (expand-file-name "checksums.dat" org-mobile-directory))
522 (files org-mobile-checksum-files)
523 entry file sum)
524 (with-temp-file sumfile
525 (set-buffer-file-coding-system 'undecided-unix nil)
526 (while (setq entry (pop files))
527 (setq file (car entry) sum (cdr entry))
528 (insert (format "%s %s\n" sum file))))))
8d642074
CD
529
530(defun org-mobile-sumo-agenda-command ()
531 "Return an agenda custom command that comprises all custom commands."
532 (let ((custom-list
533 ;; normalize different versions
534 (delq nil
535 (mapcar
536 (lambda (x)
537 (cond ((stringp (cdr x)) nil)
538 ((stringp (nth 1 x)) x)
539 ((not (nth 1 x)) (cons (car x) (cons "" (cddr x))))
540 (t (cons (car x) (cons "" (cdr x))))))
541 org-agenda-custom-commands)))
ed21c5c8 542 (default-list '(("a" "Agenda" agenda) ("t" "All TODO" alltodo)))
c7cf0ebc 543 thelist atitle new e key desc type match settings cmds gkey gdesc gsettings cnt)
ed21c5c8
CD
544 (cond
545 ((eq org-mobile-agendas 'custom)
546 (setq thelist custom-list))
547 ((eq org-mobile-agendas 'default)
548 (setq thelist default-list))
549 ((eq org-mobile-agendas 'all)
550 (setq thelist custom-list)
551 (unless (assoc "t" thelist) (push '("t" "ALL TODO" alltodo) thelist))
552 (unless (assoc "a" thelist) (push '("a" "Agenda" agenda) thelist)))
553 ((listp org-mobile-agendas)
554 (setq thelist (append custom-list default-list))
555 (setq thelist (delq nil (mapcar (lambda (k) (assoc k thelist))
556 org-mobile-agendas)))))
557 (while (setq e (pop thelist))
8d642074
CD
558 (cond
559 ((stringp (cdr e))
560 ;; this is a description entry - skip it
561 )
562 ((eq (nth 2 e) 'search)
563 ;; Search view is interactive, skip
564 )
565 ((memq (nth 2 e) '(todo-tree tags-tree occur-tree))
566 ;; These are trees, not really agenda commands
567 )
ed21c5c8
CD
568 ((and (memq (nth 2 e) '(todo tags tags-todo))
569 (or (null (nth 3 e))
570 (not (string-match "\\S-" (nth 3 e)))))
571 ;; These would be interactive because the match string is empty
572 )
573 ((memq (nth 2 e) '(agenda alltodo todo tags tags-todo))
8d642074
CD
574 ;; a normal command
575 (setq key (car e) desc (nth 1 e) type (nth 2 e) match (nth 3 e)
576 settings (nth 4 e))
577 (setq settings
578 (cons (list 'org-agenda-title-append
8bfe682a 579 (concat "<after>KEYS=" key " TITLE: "
8d642074
CD
580 (if (and (stringp desc) (> (length desc) 0))
581 desc (symbol-name type))
8223b1d2 582 "</after>"))
8d642074
CD
583 settings))
584 (push (list type match settings) new))
3ab2c837
BG
585 ((or (functionp (nth 2 e)) (symbolp (nth 2 e)))
586 ;; A user-defined function, which can do anything, so simply
587 ;; ignore it.
8d642074
CD
588 )
589 (t
590 ;; a block agenda
591 (setq gkey (car e) gdesc (nth 1 e) gsettings (nth 3 e) cmds (nth 2 e))
592 (setq cnt 0)
593 (while (setq e (pop cmds))
594 (setq type (car e) match (nth 1 e) settings (nth 2 e))
c7cf0ebc 595 (setq atitle (if (string= "" gdesc) match gdesc))
8d642074
CD
596 (setq settings (append gsettings settings))
597 (setq settings
598 (cons (list 'org-agenda-title-append
8bfe682a 599 (concat "<after>KEYS=" gkey "#" (number-to-string
8223b1d2 600 (setq cnt (1+ cnt)))
c7cf0ebc 601 " TITLE: " atitle "</after>"))
8d642074
CD
602 settings))
603 (push (list type match settings) new)))))
8bfe682a
CD
604 (and new (list "X" "SUMO" (reverse new)
605 '((org-agenda-compact-blocks nil))))))
606
607(defvar org-mobile-creating-agendas nil)
608(defun org-mobile-write-agenda-for-mobile (file)
609 (let ((all (buffer-string)) in-date id pl prefix line app short m sexp)
610 (with-temp-file file
611 (org-mode)
612 (insert "#+READONLY\n")
613 (insert all)
614 (goto-char (point-min))
615 (while (not (eobp))
616 (cond
617 ((looking-at "[ \t]*$")) ; keep empty lines
618 ((looking-at "=+$")
619 ;; remove underlining
620 (delete-region (point) (point-at-eol)))
621 ((get-text-property (point) 'org-agenda-structural-header)
622 (setq in-date nil)
a89c8ef0
BG
623 (setq app (get-text-property (point) 'org-agenda-title-append))
624 (setq short (get-text-property (point) 'short-heading))
8bfe682a 625 (when (and short (looking-at ".+"))
a89c8ef0 626 (replace-match short nil t)
8bfe682a
CD
627 (beginning-of-line 1))
628 (when app
629 (end-of-line 1)
630 (insert app)
631 (beginning-of-line 1))
632 (insert "* "))
633 ((get-text-property (point) 'org-agenda-date-header)
634 (setq in-date t)
635 (insert "** "))
636 ((setq m (or (get-text-property (point) 'org-hd-marker)
637 (get-text-property (point) 'org-marker)))
638 (setq sexp (member (get-text-property (point) 'type)
639 '("diary" "sexp")))
3ab2c837 640 (if (setq pl (text-property-any (point) (point-at-eol) 'org-heading t))
8bfe682a
CD
641 (progn
642 (setq prefix (org-trim (buffer-substring
3ab2c837 643 (point) pl))
8bfe682a 644 line (org-trim (buffer-substring
3ab2c837 645 pl
8bfe682a
CD
646 (point-at-eol))))
647 (delete-region (point-at-bol) (point-at-eol))
648 (insert line "<before>" prefix "</before>")
649 (beginning-of-line 1))
650 (and (looking-at "[ \t]+") (replace-match "")))
651 (insert (if in-date "*** " "** "))
652 (end-of-line 1)
653 (insert "\n")
654 (unless sexp
655 (insert (org-agenda-get-some-entry-text
656 m 10 " " 'planning)
657 "\n")
658 (when (setq id
659 (if (org-bound-and-true-p
660 org-mobile-force-id-on-agenda-items)
661 (org-id-get m 'create)
afe98dfa
CD
662 (or (org-entry-get m "ID")
663 (org-mobile-get-outline-path-link m))))
8bfe682a
CD
664 (insert " :PROPERTIES:\n :ORIGINAL_ID: " id
665 "\n :END:\n")))))
666 (beginning-of-line 2))
afe98dfa 667 (push (cons "agendas.org" (md5 (buffer-string)))
8bfe682a
CD
668 org-mobile-checksum-files))
669 (message "Agenda written to Org file %s" file)))
8d642074 670
afe98dfa
CD
671(defun org-mobile-get-outline-path-link (pom)
672 (org-with-point-at pom
673 (concat "olp:"
674 (org-mobile-escape-olp (file-name-nondirectory buffer-file-name))
675 "/"
676 (mapconcat 'org-mobile-escape-olp
677 (org-get-outline-path)
678 "/")
679 "/"
680 (org-mobile-escape-olp (nth 4 (org-heading-components))))))
681
682(defun org-mobile-escape-olp (s)
3ab2c837 683 (let ((table '(?: ?/)))
afe98dfa
CD
684 (org-link-escape s table)))
685
8d642074
CD
686(defun org-mobile-create-sumo-agenda ()
687 "Create a file that contains all custom agenda views."
688 (interactive)
689 (let* ((file (expand-file-name "agendas.org"
690 org-mobile-directory))
ed21c5c8
CD
691 (file1 (if org-mobile-use-encryption
692 org-mobile-encryption-tempfile
693 file))
8bfe682a 694 (sumo (org-mobile-sumo-agenda-command))
8d642074 695 (org-agenda-custom-commands
ed21c5c8 696 (list (append sumo (list (list file1)))))
8bfe682a 697 (org-mobile-creating-agendas t))
ed21c5c8
CD
698 (unless (file-writable-p file1)
699 (error "Cannot write to file %s" file1))
8bfe682a 700 (when sumo
ed21c5c8
CD
701 (org-store-agenda-views))
702 (when org-mobile-use-encryption
afe98dfa
CD
703 (org-mobile-encrypt-and-move file1 file)
704 (delete-file file1)
705 (org-mobile-cleanup-encryption-tempfile))))
ed21c5c8
CD
706
707(defun org-mobile-encrypt-and-move (infile outfile)
708 "Encrypt INFILE locally to INFILE_enc, then move it to OUTFILE.
709We do this in two steps so that remote paths will work, even if the
710encryption program does not understand them."
711 (let ((encfile (concat infile "_enc")))
712 (org-mobile-encrypt-file infile encfile)
713 (when outfile
714 (copy-file encfile outfile 'ok-if-exists)
715 (delete-file encfile))))
716
717(defun org-mobile-encrypt-file (infile outfile)
718 "Encrypt INFILE to OUTFILE, using `org-mobile-encryption-password'."
719 (shell-command
720 (format "openssl enc -aes-256-cbc -salt -pass %s -in %s -out %s"
afe98dfa
CD
721 (shell-quote-argument (concat "pass:"
722 (org-mobile-encryption-password)))
ed21c5c8
CD
723 (shell-quote-argument (expand-file-name infile))
724 (shell-quote-argument (expand-file-name outfile)))))
725
726(defun org-mobile-decrypt-file (infile outfile)
727 "Decrypt INFILE to OUTFILE, using `org-mobile-encryption-password'."
728 (shell-command
729 (format "openssl enc -d -aes-256-cbc -salt -pass %s -in %s -out %s"
afe98dfa
CD
730 (shell-quote-argument (concat "pass:"
731 (org-mobile-encryption-password)))
ed21c5c8
CD
732 (shell-quote-argument (expand-file-name infile))
733 (shell-quote-argument (expand-file-name outfile)))))
8d642074 734
afe98dfa
CD
735(defun org-mobile-cleanup-encryption-tempfile ()
736 "Remove the encryption tempfile if it exists."
737 (and (stringp org-mobile-encryption-tempfile)
738 (file-exists-p org-mobile-encryption-tempfile)
739 (delete-file org-mobile-encryption-tempfile)))
740
8d642074
CD
741(defun org-mobile-move-capture ()
742 "Move the contents of the capture file to the inbox file.
743Return a marker to the location where the new content has been added.
8bfe682a 744If nothing new has been added, return nil."
8d642074 745 (interactive)
ed21c5c8
CD
746 (let* ((encfile nil)
747 (capture-file (expand-file-name org-mobile-capture-file
748 org-mobile-directory))
749 (inbox-buffer (find-file-noselect org-mobile-inbox-for-pull))
750 (capture-buffer
751 (if (not org-mobile-use-encryption)
752 (find-file-noselect capture-file)
afe98dfa 753 (org-mobile-cleanup-encryption-tempfile)
ed21c5c8
CD
754 (setq encfile (concat org-mobile-encryption-tempfile "_enc"))
755 (copy-file capture-file encfile)
756 (org-mobile-decrypt-file encfile org-mobile-encryption-tempfile)
757 (find-file-noselect org-mobile-encryption-tempfile)))
758 (insertion-point (make-marker))
759 not-empty content)
81ad75af 760 (with-current-buffer capture-buffer
8d642074
CD
761 (setq content (buffer-string))
762 (setq not-empty (string-match "\\S-" content))
763 (when not-empty
764 (set-buffer inbox-buffer)
765 (widen)
766 (goto-char (point-max))
767 (or (bolp) (newline))
768 (move-marker insertion-point
769 (prog1 (point) (insert content)))
770 (save-buffer)
771 (set-buffer capture-buffer)
772 (erase-buffer)
8bfe682a
CD
773 (save-buffer)
774 (org-mobile-update-checksum-for-capture-file (buffer-string))))
8d642074 775 (kill-buffer capture-buffer)
ed21c5c8
CD
776 (when org-mobile-use-encryption
777 (org-mobile-encrypt-and-move org-mobile-encryption-tempfile
afe98dfa
CD
778 capture-file)
779 (org-mobile-cleanup-encryption-tempfile))
8d642074
CD
780 (if not-empty insertion-point)))
781
8bfe682a 782(defun org-mobile-update-checksum-for-capture-file (buffer-string)
ed21c5c8 783 "Find the checksum line and modify it to match BUFFER-STRING."
8bfe682a
CD
784 (let* ((file (expand-file-name "checksums.dat" org-mobile-directory))
785 (buffer (find-file-noselect file)))
786 (when buffer
787 (with-current-buffer buffer
788 (when (re-search-forward (concat "\\([0-9a-fA-F]\\{30,\\}\\).*?"
789 (regexp-quote org-mobile-capture-file)
790 "[ \t]*$") nil t)
791 (goto-char (match-beginning 1))
792 (delete-region (match-beginning 1) (match-end 1))
793 (insert (md5 buffer-string))
794 (save-buffer)))
795 (kill-buffer buffer))))
796
797(defun org-mobile-apply (&optional beg end)
798 "Apply all change requests in the current buffer.
8d642074
CD
799If BEG and END are given, only do this in that region."
800 (interactive)
801 (require 'org-archive)
802 (setq org-mobile-last-flagged-files nil)
803 (setq beg (or beg (point-min)) end (or end (point-max)))
8bfe682a
CD
804
805 ;; Remove all Note IDs
8d642074 806 (goto-char beg)
8bfe682a
CD
807 (while (re-search-forward "^\\*\\* Note ID: [-0-9A-F]+[ \t]*\n" end t)
808 (replace-match ""))
809
810 ;; Find all the referenced entries, without making any changes yet
8d642074 811 (let ((marker (make-marker))
8bfe682a 812 (bos-marker (make-marker))
8d642074 813 (end (move-marker (make-marker) end))
8bfe682a
CD
814 (cnt-new 0)
815 (cnt-edit 0)
816 (cnt-flag 0)
817 (cnt-error 0)
818 buf-list
819 id-pos org-mobile-error)
820
821 ;; Count the new captures
822 (goto-char beg)
823 (while (re-search-forward "^\\* \\(.*\\)" end t)
824 (and (>= (- (match-end 1) (match-beginning 1)) 2)
825 (not (equal (downcase (substring (match-string 1) 0 2)) "f("))
826 (incf cnt-new)))
827
8223b1d2 828 ;; Find and apply the edits
8bfe682a 829 (goto-char beg)
8d642074 830 (while (re-search-forward
8bfe682a 831 "^\\*+[ \t]+F(\\([^():\n]*\\)\\(:\\([^()\n]*\\)\\)?)[ \t]+\\[\\[\\(\\(id\\|olp\\):\\([^]\n]+\\)\\)" end t)
8d642074 832 (catch 'next
8223b1d2
BG
833 (let* ((action (match-string 1))
834 (data (and (match-end 3) (match-string 3)))
835 (id-pos (condition-case msg
836 (org-mobile-locate-entry (match-string 4))
837 (error (nth 1 msg))))
838 (bos (point-at-bol))
839 (eos (save-excursion (org-end-of-subtree t t)))
840 (cmd (if (equal action "")
841 '(progn
842 (incf cnt-flag)
843 (org-toggle-tag "FLAGGED" 'on)
844 (and note
845 (org-entry-put nil "THEFLAGGINGNOTE" note)))
846 (incf cnt-edit)
847 (cdr (assoc action org-mobile-action-alist))))
848 (note (and (equal action "")
849 (buffer-substring (1+ (point-at-eol)) eos)))
850 (org-inhibit-logging 'note) ;; Do not take notes interactively
851 old new)
852
853 (goto-char bos)
854 (when (and (markerp id-pos)
855 (not (member (marker-buffer id-pos) buf-list)))
856 (org-mobile-timestamp-buffer (marker-buffer id-pos))
857 (push (marker-buffer id-pos) buf-list))
858 (unless (markerp id-pos)
859 (goto-char (+ 2 (point-at-bol)))
860 (if (stringp id-pos)
861 (insert id-pos " ")
862 (insert "BAD REFERENCE "))
863 (incf cnt-error)
864 (throw 'next t))
865 (unless cmd
866 (insert "BAD FLAG ")
867 (incf cnt-error)
868 (throw 'next t))
869 (move-marker bos-marker (point))
870 (if (re-search-forward "^** Old value[ \t]*$" eos t)
871 (setq old (buffer-substring
872 (1+ (match-end 0))
873 (progn (outline-next-heading) (point)))))
874 (if (re-search-forward "^** New value[ \t]*$" eos t)
875 (setq new (buffer-substring
876 (1+ (match-end 0))
877 (progn (outline-next-heading)
878 (if (eobp) (org-back-over-empty-lines))
879 (point)))))
880 (setq old (and old (if (string-match "\\S-" old) old nil)))
881 (setq new (and new (if (string-match "\\S-" new) new nil)))
882 (if (and note (> (length note) 0))
883 ;; Make Note into a single line, to fit into a property
884 (setq note (mapconcat 'identity
885 (org-split-string (org-trim note) "\n")
886 "\\n")))
887 (unless (equal data "body")
888 (setq new (and new (org-trim new))
889 old (and old (org-trim old))))
890 (goto-char (+ 2 bos-marker))
891 ;; Remember this place so that we can return
892 (move-marker marker (point))
893 (setq org-mobile-error nil)
894 (save-excursion
895 (condition-case msg
896 (org-with-point-at id-pos
897 (progn
898 (eval cmd)
899 (unless (member data (list "delete" "archive" "archive-sibling" "addheading"))
900 (if (member "FLAGGED" (org-get-tags))
901 (add-to-list 'org-mobile-last-flagged-files
902 (buffer-file-name (current-buffer)))))))
903 (error (setq org-mobile-error msg))))
904 (when org-mobile-error
905 (org-pop-to-buffer-same-window (marker-buffer marker))
906 (goto-char marker)
907 (incf cnt-error)
908 (insert (if (stringp (nth 1 org-mobile-error))
909 (nth 1 org-mobile-error)
910 "EXECUTION FAILED")
911 " ")
912 (throw 'next t))
913 ;; If we get here, the action has been applied successfully
914 ;; So remove the entry
915 (goto-char bos-marker)
916 (delete-region (point) (org-end-of-subtree t t)))))
8bfe682a 917 (save-buffer)
8d642074 918 (move-marker marker nil)
8bfe682a
CD
919 (move-marker end nil)
920 (message "%d new, %d edits, %d flags, %d errors" cnt-new
921 cnt-edit cnt-flag cnt-error)
922 (sit-for 1)))
923
924(defun org-mobile-timestamp-buffer (buf)
925 "Time stamp buffer BUF, just to make sure its checksum will change."
926 (with-current-buffer buf
927 (save-excursion
928 (save-restriction
929 (widen)
930 (goto-char (point-min))
931 (if (re-search-forward
932 "^\\([ \t]*\\)#\\+LAST_MOBILE_CHANGE:.*\n?" nil t)
933 (progn
934 (goto-char (match-end 1))
935 (delete-region (point) (match-end 0)))
936 (if (looking-at ".*?-\\*-.*-\\*-")
937 (forward-line 1)))
938 (insert "#+LAST_MOBILE_CHANGE: "
939 (format-time-string "%Y-%m-%d %T") "\n")))))
8d642074
CD
940
941(defun org-mobile-smart-read ()
942 "Parse the entry at point for shortcuts and expand them.
943These shortcuts are meant for fast and easy typing on the limited
944keyboards of a mobile device. Below we show a list of the shortcuts
945currently implemented.
946
947The entry is expected to contain an inactive time stamp indicating when
948the entry was created. When setting dates and
949times (for example for deadlines), the time strings are interpreted
950relative to that creation date.
8bfe682a 951Abbreviations are expected to take up entire lines, just because it is so
8d642074
CD
952easy to type RET on a mobile device. Abbreviations start with one or two
953letters, followed immediately by a dot and then additional information.
954Generally the entire shortcut line is removed after action have been taken.
955Time stamps will be constructed using `org-read-date'. So for example a
956line \"dd. 2tue\" will set a deadline on the second Tuesday after the
957creation date.
958
959Here are the shortcuts currently implemented:
960
961dd. string set deadline
962ss. string set scheduling
963tt. string set time tamp, here.
964ti. string set inactive time
965
966tg. tag1 tag2 tag3 set all these tags, change case where necessary
967td. kwd set this todo keyword, change case where necessary
968
969FIXME: Hmmm, not sure if we can make his work against the
970auto-correction feature. Needs a bit more thinking. So this function
971is currently a noop.")
972
8bfe682a
CD
973(defun org-mobile-locate-entry (link)
974 (if (string-match "\\`id:\\(.*\\)$" link)
975 (org-id-find (match-string 1 link) 'marker)
976 (if (not (string-match "\\`olp:\\(.*?\\):\\(.*\\)$" link))
8223b1d2
BG
977 ; not found with path, but maybe it is to be inserted
978 ; in top level of the file?
979 (if (not (string-match "\\`olp:\\(.*?\\)$" link))
980 nil
981 (let ((file (match-string 1 link)))
982 (setq file (org-link-unescape file))
983 (setq file (expand-file-name file org-directory))
984 (save-excursion
985 (find-file file)
986 (goto-char (point-max))
987 (newline)
988 (goto-char (point-max))
c7cf0ebc 989 (point-marker))))
8bfe682a 990 (let ((file (match-string 1 link))
3ab2c837
BG
991 (path (match-string 2 link)))
992 (setq file (org-link-unescape file))
8bfe682a 993 (setq file (expand-file-name file org-directory))
3ab2c837 994 (setq path (mapcar 'org-link-unescape
8bfe682a
CD
995 (org-split-string path "/")))
996 (org-find-olp (cons file path))))))
997
998(defun org-mobile-edit (what old new)
999 "Edit item WHAT in the current entry by replacing OLD with NEW.
1000WHAT can be \"heading\", \"todo\", \"tags\", \"priority\", or \"body\".
1001The edit only takes place if the current value is equal (except for
1002white space) the OLD. If this is so, OLD will be replace by NEW
1003and the command will return t. If something goes wrong, a string will
1004be returned that indicates what went wrong."
8223b1d2 1005 (let (current old1 new1 level)
8bfe682a
CD
1006 (if (stringp what) (setq what (intern what)))
1007
1008 (cond
1009
1010 ((memq what '(todo todostate))
1011 (setq current (org-get-todo-state))
1012 (cond
1013 ((equal new "DONEARCHIVE")
1014 (org-todo 'done)
1015 (org-archive-subtree-default))
1016 ((equal new current) t) ; nothing needs to be done
1017 ((or (equal current old)
1018 (eq org-mobile-force-mobile-change t)
1019 (memq 'todo org-mobile-force-mobile-change))
1020 (org-todo (or new 'none)) t)
1021 (t (error "State before change was expected as \"%s\", but is \"%s\""
1022 old current))))
ed21c5c8 1023
8bfe682a
CD
1024 ((eq what 'tags)
1025 (setq current (org-get-tags)
1026 new1 (and new (org-split-string new ":+"))
1027 old1 (and old (org-split-string old ":+")))
1028 (cond
1029 ((org-mobile-tags-same-p current new1) t) ; no change needed
1030 ((or (org-mobile-tags-same-p current old1)
1031 (eq org-mobile-force-mobile-change t)
1032 (memq 'tags org-mobile-force-mobile-change))
1033 (org-set-tags-to new1) t)
1034 (t (error "Tags before change were expected as \"%s\", but are \"%s\""
1035 (or old "") (or current "")))))
ed21c5c8 1036
8bfe682a
CD
1037 ((eq what 'priority)
1038 (when (looking-at org-complex-heading-regexp)
1039 (setq current (and (match-end 3) (substring (match-string 3) 2 3)))
1040 (cond
1041 ((equal current new) t) ; no action required
1042 ((or (equal current old)
1043 (eq org-mobile-force-mobile-change t)
1044 (memq 'tags org-mobile-force-mobile-change))
1045 (org-priority (and new (string-to-char new))))
1046 (t (error "Priority was expected to be %s, but is %s"
1047 old current)))))
1048
1049 ((eq what 'heading)
1050 (when (looking-at org-complex-heading-regexp)
1051 (setq current (match-string 4))
1052 (cond
1053 ((equal current new) t) ; no action required
1054 ((or (equal current old)
1055 (eq org-mobile-force-mobile-change t)
1056 (memq 'heading org-mobile-force-mobile-change))
1057 (goto-char (match-beginning 4))
1058 (insert new)
1059 (delete-region (point) (+ (point) (length current)))
1060 (org-set-tags nil 'align))
1061 (t (error "Heading changed in MobileOrg and on the computer")))))
ed21c5c8 1062
8223b1d2
BG
1063 ((eq what 'addheading)
1064 (if (org-on-heading-p) ; if false we are in top-level of file
1065 (progn
1066 (end-of-line 1)
8a28a5b8 1067 (org-insert-heading-respect-content t)
8223b1d2
BG
1068 (org-demote))
1069 (beginning-of-line)
1070 (insert "* "))
1071 (insert new))
1072
1073 ((eq what 'refile)
1074 (org-copy-subtree)
1075 (org-with-point-at (org-mobile-locate-entry new)
1076 (if (org-on-heading-p) ; if false we are in top-level of file
1077 (progn
1078 (setq level (org-get-valid-level (funcall outline-level) 1))
1079 (org-end-of-subtree t t)
1080 (org-paste-subtree level))
1081 (org-paste-subtree 1)))
1082 (org-cut-subtree))
1083
1084 ((eq what 'delete)
1085 (org-cut-subtree))
1086
1087 ((eq what 'archive)
1088 (org-archive-subtree))
1089
1090 ((eq what 'archive-sibling)
1091 (org-archive-to-archive-sibling))
1092
8bfe682a
CD
1093 ((eq what 'body)
1094 (setq current (buffer-substring (min (1+ (point-at-eol)) (point-max))
1095 (save-excursion (outline-next-heading)
1096 (point))))
1097 (if (not (string-match "\\S-" current)) (setq current nil))
1098 (cond
1099 ((org-mobile-bodies-same-p current new) t) ; no action necessary
1100 ((or (org-mobile-bodies-same-p current old)
1101 (eq org-mobile-force-mobile-change t)
1102 (memq 'body org-mobile-force-mobile-change))
1103 (save-excursion
1104 (end-of-line 1)
1105 (insert "\n" new)
1106 (or (bolp) (insert "\n"))
1107 (delete-region (point) (progn (org-back-to-heading t)
1108 (outline-next-heading)
1109 (point))))
1110 t)
1111 (t (error "Body was changed in MobileOrg and on the computer")))))))
ed21c5c8 1112
8bfe682a
CD
1113(defun org-mobile-tags-same-p (list1 list2)
1114 "Are the two tag lists the same?"
1115 (not (or (org-delete-all list1 list2)
1116 (org-delete-all list2 list1))))
1117
1118(defun org-mobile-bodies-same-p (a b)
1119 "Compare if A and B are visually equal strings.
1120We first remove leading and trailing white space from the entire strings.
1121Then we split the strings into lines and remove leading/trailing whitespace
1122from each line. Then we compare.
1123A and B must be strings or nil."
1124 (cond
1125 ((and (not a) (not b)) t)
1126 ((or (not a) (not b)) nil)
1127 (t (setq a (org-trim a) b (org-trim b))
1128 (setq a (mapconcat 'identity (org-split-string a "[ \t]*\n[ \t]*") "\n"))
1129 (setq b (mapconcat 'identity (org-split-string b "[ \t]*\n[ \t]*") "\n"))
1130 (equal a b))))
1131
8d642074
CD
1132(provide 'org-mobile)
1133
bdebdb64
BG
1134;; Local variables:
1135;; generated-autoload-file: "org-loaddefs.el"
1136;; End:
1137
8d642074 1138;;; org-mobile.el ends here