From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.ciao.gmane.io!not-for-mail From: Alexander Adolf Newsgroups: gmane.emacs.devel Subject: [Proposal] New EUDC backend for macOS address book Date: Mon, 27 Apr 2020 17:09:07 +0200 Message-ID: Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="ciao.gmane.io:159.69.161.202"; logging-data="61531"; mail-complaints-to="usenet@ciao.gmane.io" To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Mon Apr 27 17:11:42 2020 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1jT5Q5-000Frk-KT for ged-emacs-devel@m.gmane-mx.org; Mon, 27 Apr 2020 17:11:41 +0200 Original-Received: from localhost ([::1]:50244 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jT5Q4-00041w-LU for ged-emacs-devel@m.gmane-mx.org; Mon, 27 Apr 2020 11:11:40 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:34338) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jT5Ni-0007Ve-PW for emacs-devel@gnu.org; Mon, 27 Apr 2020 11:09:15 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.90_1) (envelope-from ) id 1jT5Nh-0000kc-8T for emacs-devel@gnu.org; Mon, 27 Apr 2020 11:09:14 -0400 Original-Received: from smtprelay01.ispgateway.de ([80.67.31.35]:37063) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1jT5Ng-0000ip-Qc for emacs-devel@gnu.org; Mon, 27 Apr 2020 11:09:12 -0400 Original-Received: from [46.244.195.194] (helo=localhost) by smtprelay01.ispgateway.de with esmtpsa (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.92.3) (envelope-from ) id 1jT5Nb-0006gh-Fz; Mon, 27 Apr 2020 17:09:07 +0200 X-Df-Sender: YWxleGFuZGVyLmFkb2xmQGNvbmRpdGlvbi1hbHBoYS5jb20= Received-SPF: pass client-ip=80.67.31.35; envelope-from=alexander.adolf@condition-alpha.com; helo=smtprelay01.ispgateway.de X-detected-operating-system: by eggs.gnu.org: First seen = 2020/04/27 11:09:08 X-ACL-Warn: Detected OS = Linux 3.11 and newer [fuzzy] X-Received-From: 80.67.31.35 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:247934 Archived-At: --=-=-= Content-Type: text/plain Dear Emacs Developers, I am in the process of migrating my email workflow to `notmuch-mode' within Emacs. While `notmuch-mode' provides a completion backend for `company-mode' to get auto-completion for email addresses from your notmuch email archive, I was looking for a way to give me auto-completion for email addresses from my macOS address book, too. My research lead me to EUDC [1], and its `eudcb-mab.el', but which didn't work out of the box for me. Looking at the code, it turns out that `eudcb-mab.el' accesses the SQLite file used by macOS address book to store a local copy of the contacts, and reverse-engineers its contents. This is however not documented by Apple as an "official" way of accessing that data, and in fact Apple had recently changed the file name of the SQLite. This broke `eudcb-mab.el' for me, as it was still looking for the old file name. Also, since it is an undocumented file, Apple may choose to not only change the file name, but also its inner structure at any point. [1] https://www.gnu.org/software/emacs/manual/html_mono/eudc.html On the other hand, there is an Apple officially documented, and endorsed way of accessing the macOS address book contacts, and this is via AppleScript [2]. Being a published and documented API, it can probably be expected to remain stable, and invariant towards any redesigns of the macOS address book app that Apple may choose to undertake in the foreseeable future. [2] https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/introduction/ASLR_intro.html I hence set out to write a new backend for EUDC to get access to macOS address book contacts via AppleScript. The result is `eudcb-macos-contacts.el', which is enclosed with this message, and which I would kindly like to propose for inclusion as part of EUDC (and replacing the existing `eudcb-mab.el'). Yes, I have duly signed the copyright assignment (rt.gnu.org #1503473). In my implementation, I found that - interestingly - there is an elisp function in Emacs core, cunningly called `do_applescript()', and which is intended to execute AppleScript on the macOS platform from within Emacs. Unfortunately, I did find some oddities with it (see debbugs #39890 [3]), but couldn't discern whether the cause was in `do_applescript()' itself (so a fix could have been proposed), or in the Apple library code it builds upon. I hence decided to instead use `call-process()' to invoke the osascript [4] command line utility, which ships as part of every macOS. This does work reliably for me, and yields a more graceful overall behaviour of Emacs during large queries (again see debbugs #39890 [3]). [3] https://debbugs.gnu.org/cgi/bugreport.cgi?bug=39890 [4] https://ss64.com/osx/osascript.html Many thanks in advance for your considerations, and looking forward to your thoughts, --alexander --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=eudcb-macos-contacts.el Content-Transfer-Encoding: quoted-printable ;;; eudcb-macos-contacts.el --- Emacs Unified Directory Client - macOS Cont= acts backend ;; Copyright (C) 2020 condition-alpha.com ;; This program 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 3 of the License, or ;; (at your option) any later version. ;; ;; This program 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 this program. If not, see . ;;; Commentary: ;; This library provides an interface to the macOS Contacts app as ;; an EUDC data source. It uses AppleScript to interface with the ;; Contacts app on localhost, so no 3rd party tools are needed. ;;; Usage: ;; (require 'eudcb-macos-contacts) ;; (eudc-macos-contacts-set-server "localhost") ;;; Code: (require 'eudc) (require 'executable) ;;{{{ Internal cooking (defvar eudc-macos-contacts-conversion-alist nil) ;; hook ourselves into the EUDC framework (eudc-protocol-set 'eudc-query-function 'eudc-macos-contacts-query-internal 'macos-contacts) (eudc-protocol-set 'eudc-list-attributes-function nil 'macos-contacts) (eudc-protocol-set 'eudc-macos-contacts-conversion-alist nil 'macos-contacts) (eudc-protocol-set 'eudc-protocol-has-default-query-attributes nil 'macos-contacts) (defun eudc-macos-contacts-search-helper (str) "Helper function to query the Contacts app via AppleScript. Searches for all persons with a case-insensitive substring match of STR in any of their name fileds (first, middle, or last)." (if (executable-find "osascript") (call-process "osascript" nil t nil "-e" (format " set results to {} tell application \"Contacts\" set pList to every person whose (name contains \"%s\") repeat with pers in pList repeat with emailAddr in emails of pers set results to results & {name of pers & \":\" & value of emailAddr & \"= \n\"} end repeat end repeat get results as text end tell" str)) (message "[eudc] Error in macOS Contacts: `osascript' executable not fo= und"))) (defun eudc-macos-contacts-query-internal (query &optional return-attrs) "Query macOS Contacts with QUERY. QUERY is a list of cons cells (ATTR . VALUE) where ATTRs should be valid macOS Contacts attribute names. RETURN-ATTRS is a list of attributes to return, defaulting to `eudc-default-return-attributes'." (let ((macos-contacts-buffer (get-buffer-create " *macOS Contacts*")) result) (with-current-buffer macos-contacts-buffer (erase-buffer) (dolist (term query) (eudc-macos-contacts-search-helper (cdr term))) (delete-duplicate-lines (point-min) (point-max)) (goto-char (point-min)) (while (not (eobp)) (if (not (equal (line-beginning-position) (line-end-position))) (let* ((args (split-string (buffer-substring (point) (line-end-position)) ":")) (name (nth 0 args)) (email (nth 1 args))) (setq result (cons `((name . ,name) (email . ,email)) result)))) (forward-line)) result))) ;;}}} ;;{{{ High-level interfaces (interactive functions) (defun eudc-macos-contacts-set-server (dummy) "Set the EUDC server to macOS Contacts app. The server in DUMMY is not actually used, since this backend always and implicitly connetcs to an instance of the Contacts app running on the local host." (interactive) (eudc-set-server dummy 'macos-contacts) (message "[eudc] macOS Contacts app server selected")) ;;}}} (eudc-register-protocol 'macos-contacts) (provide 'eudcb-macos-contacts) ;;; eudcb-macos-contacts.el ends here --=-=-=--