girzel / ebdb

An EIEIO port of BBDB, Emacs' contact-management package
67 stars 11 forks source link

migration from org-contacts? #56

Closed jave closed 6 years ago

jave commented 6 years ago

currently i have my contacts in an org-contacts file. I'm looking at converting to ebdb. Is there any way to do this already?

girzel commented 6 years ago

Nope, I don't think anyone's ever done this. It's something I'd like to support, though, if you're willing to be a guinea pig :)

girzel commented 6 years ago

Quick question: do you have contacts with multiple values for single property types? Meaning, more than one email address, for instance? How does that look in the Org file?

girzel commented 6 years ago

Here's what I've got so far. Unfortunately I don't know how to parse street addresses, in part because I don't know how org-contacts stores them, but also just because it's very hard to read an address from a free-form string. The current implementation just sticks the addresses in another buffer, and pops that up; you'd have to add them yourself. I'll try to figure out a better solution to that.

(defvar ebdb-migrate-org-simple-correspondences
  '((org-contacts-email-property . ebdb-default-mail-class)
    (org-contacts-tel-property . ebdb-default-phone-class)
    (org-contacts-note-property . ebdb-default-notes-class)
    (org-contacts-nickname-property . ebdb-field-name-simple)
    (org-contacts-alias-property . ebdb-default-name-class))
  "The simplest property-to-field correspondences.
This variable only holds correspondences for fields that require
no processing beyond calling `ebdb-parse' on the string values.")

(defun ebdb-migrate-from-org-contacts ()
  "Migrate contacts from the org-contacts format."
  (interactive)
  (require 'org-contacts)
  (unless ebdb-sources
    (error "First set `ebdb-sources' to the location of your EBDB database."))
  (let ((db (ebdb-prompt-for-db))
    ;; Postpone evaluation of the symbols until run time.
    (prop-fields
     (mapcar
      (pcase-lambda (`(,prop . ,class))
        (cons (symbol-value prop)
          (if (class-p class) class (symbol-value class))))
      ebdb-migrate-org-simple-correspondences))
    (count 0)
    (address-buffer (get-buffer-create "*EBDB Migration Addresses*"))
    record records n f)
    (with-current-buffer address-buffer
      (erase-buffer))
    (message "Migrating records from org-contacts... %d records" count)
    (pcase-dolist (`(,name ,_ ,fields) (org-contacts-db))
      (setq record (make-instance ebdb-default-record-class))
      (setq n (ebdb-parse ebdb-default-name-class name))
      (ebdb-record-change-name record n)
      (dolist (field fields)
    (setq f
          (if (assoc-string (car field) prop-fields)
          (ebdb-parse (cdr (assoc-string (car field) prop-fields))
                  (cdr field))
        (pcase (car field)
          ((pred (equal org-contacts-address-property))
           (with-current-buffer address-buffer
             (insert (format "%s: %s\n" name (cdr field)))))
          ((pred (equal org-contacts-birthday-property))
           (make-instance 'ebdb-field-anniversary
                  :date  (calendar-gregorian-from-absolute
                      (org-time-string-to-absolute
                       (cdr field)))
                  :object-name "birthday")))))
    (when f
      (ebdb-record-insert-field record f)))
      (push record records)
      (message "Migrating records from org-contacts... %d records"
           (cl-incf count)))
    (dolist (r records)
      (ebdb-db-add-record db r))
    (message "Migrating records from org-contacts... %d records"
         (length records)))
  (ebdb-display-records records)
  (with-current-buffer address-buffer
    (unless (= (point-min) (point-max))
      (pop-to-buffer address-buffer
             '(display-buffer-pop-up-window . ((width . 50)))))))
jave commented 6 years ago

thanks, I will try it out.

atm my contacts db is spread out over a lot of org files, and an old bbdb file, so it will take a lot of time to convert to ebdb.

girzel commented 6 years ago

It shouldn't be too hard, org-contacts-db will find all the contacts in your agenda files, you don't have to do it file by file.

One call to ebdb-migrate-from-bbdb and another to ebdb-migrate-from-org-contacts should do it.

If you can show me an example of what an address property looks like, I might be able to handle those automatically, too.

jave commented 6 years ago

ebdb-contacts-db lacks function definition.

girzel commented 6 years ago

Sorry! That should be org-contacts-db! My brain stopped for a moment.

jave commented 6 years ago

ebdb-migrate-from-org-contacts Migrating 0 records... Updating Org Contacts Database...done cl-no-applicable-method: No applicable method: ebdb-record-change-name, #s(#s(eieio--class ebdb-record-person "A record class representing a person." (#s(eieio--class ebdb-record-entity "An abstract class representing basic entities that have mail, phone and address fields." (#s(eieio--class ebdb-record "An abstract base class for creating EBDB records." (#s(eieio--class eieio-instance-tracker "This special class enables instance tracking. Inheritors from this class must overload `tracking-symbol' which is a variable symbol used to store a list of all instances." nil [] #s(hash-table size 65 test eq rehash-size 1.5 rehash-threshold 0.8125 data ( ...)) (ebdb-formatter ebdb-record semanticdb-project-database) nil [#s(cl-slot-descriptor tracking-symbol unbound symbol ...)] [unbound] ...)) [#s(cl-slot-descriptor uuid nil (or null ebdb-field-uuid) nil) #s(cl-slot-descriptor creation-date nil (or null ebdb-field-creation-date) nil) #s(cl-slot-descriptor timestamp nil (or null ebdb-field-timestamp) nil) #s(cl-slot-descriptor fields nil (list-of ebdb-field-user) ((:documentation . "This slot contains all record fields except those built-in to record subclasses."))) #s(cl-slot-descriptor image nil (or null ebdb-field-image) nil) #s(cl-slot-descriptor notes nil (or null ebdb-field-notes) ((:documentation . "User notes for this contact."))) #s(cl-slot-descriptor dirty nil boolean ((:documentation . "Does this record have changed fields?"))) #s(cl-slot-descriptor cache nil (or null ebdb-cache) nil)] #s(hash-table size 65 test eq rehash-size 1.5 rehash-threshold 0.8125 data (uuid 0 creation-date 1 timestamp 2 fields 3 image 4 notes 5 dirty 6 cache 7 ...)) (ebdb-record-mailing-list ebdb-record-entity) ((:uuid . uuid) (:creation-date . creation-date) (:timestamp . timestamp) (:fields . fields) (:image . image) (:notes . notes) (:dirty . dirty) (:cache . cache)) [#s(cl-slot-descriptor tracking-symbol ebdb-record-tracker symbol ((:documentation . "The symbol used to maintain a list of our instances. The instance list is treated as a variable, with new instances added to it.")))] [ebdb-record-tracker] ...)) [#s(cl-slot-descriptor uuid nil (or null ebdb-field-uuid) nil) #s(cl-slot-descriptor creation-date nil (or null ebdb-field-creation-date) nil) #s(cl-slot-descriptor timestamp nil (or null ebdb-field-timestamp) nil) #s(cl-slot-descriptor fields nil (list-of ebdb-field-user) ((:documentation . "This slot contains all record fields except those built-in to record subclasses."))) #s(cl-slot-descriptor image nil (or null ebdb-field-image) nil) #s(cl-slot-descriptor notes nil (or null ebdb-field-notes) ((:documentation . "User notes for this contact."))) #s(cl-slot-descriptor dirty nil boolean ((:documentation . "Does this record have changed fields?"))) #s(cl-slot-descriptor cache nil (or null ebdb-cache) nil) #s(cl-slot-descriptor mail nil (list-of ebdb-field-mail) nil) #s(cl-slot-descriptor phone nil (list-of ebdb-field-phone) nil) ...] #s(hash-table size 65 test eq rehash-size 1.5 rehash-threshold 0.8125 data (uuid 0 creation-date 1 timestamp 2 fields 3 image 4 notes 5 dirty 6 cache 7 mail 8 phone 9 ...)) (ebdb-record-organization ebdb-record-person) ((:uuid . uuid) (:creation-date . creation-date) (:timestamp . timestamp) (:fields . fields) (:image . image) (:notes . notes) (:dirty . dirty) (:cache . cache) (:mail . mail) (:phone . phone) ...) [#s(cl-slot-descriptor tracking-symbol ebdb-record-tracker symbol ((:documentation . "The symbol used to maintain a list of our instances. The instance list is treated as a variable, with new instances added to it.")))] [ebdb-record-tracker] ...)) [#s(cl-slot-descriptor uuid nil (or null ebdb-field-uuid) nil) #s(cl-slot-descriptor creation-date nil (or null ebdb-field-creation-date) nil) #s(cl-slot-descriptor timestamp nil (or null ebdb-field-timestamp) nil) #s(cl-slot-descriptor fields nil (list-of ebdb-field-user) ((:documentation . "This slot contains all record fields except those built-in to record subclasses."))) #s(cl-slot-descriptor image nil (or null ebdb-field-image) nil) #s(cl-slot-descriptor notes nil (or null ebdb-field-notes) ((:documentation . "User notes for this contact."))) #s(cl-slot-descriptor dirty nil boolean ((:documentation . "Does this record have changed fields?"))) #s(cl-slot-descriptor cache nil (or null ebdb-cache) nil) #s(cl-slot-descriptor mail nil (list-of ebdb-field-mail) nil) #s(cl-slot-descriptor phone nil (list-of ebdb-field-phone) nil) ...] #s(hash-table size 65 test eq rehash-size 1.5 rehash-threshold 0.8125 data (uuid 0 creation-date 1 timestamp 2 fields 3 image 4 notes 5 dirty 6 cache 7 mail 8 phone 9 ...)) nil ((:uuid . uuid) (:creation-date . creation-date) (:timestamp . timestamp) (:fields . fields) (:image . image) (:notes . notes) (:dirty . dirty) (:cache . cache) (:mail . mail) (:phone . phone) ...) [#s(cl-slot-descriptor tracking-symbol ebdb-record-tracker symbol ((:documentation . "The symbol used to maintain a list of our instances. The instance list is treated as a variable, with new instances added to it.")))] [ebdb-record-tracker] ...) nil #s(#s(eieio--class ebdb-field-creation-date nil (#s(eieio--class ebdb-field-timestamp nil (#s(eieio--class ebdb-field "Abstract class for EBDB fields. Subclass this to produce real field types." nil [] #s(hash-table size 65 test eq rehash-size 1.5 rehash-threshold 0.8125 data ( ...)) (ebdb-field-domain ebdb-field-image ebdb-field-relation ebdb-field-timestamp ebdb-field-notes ebdb-field-phone ebdb-field-address ebdb-field-mail ebdb-field-role ebdb-field-name ...) nil [#s(cl-slot-descriptor actions nil (list-of cons) (...))] [nil] ...)) [#s(cl-slot-descriptor timestamp nil list nil)] #s(hash-table size 65 test eq rehash-size 1.5 rehash-threshold 0.8125 data (timestamp 0 ...)) (ebdb-field-creation-date) ((:timestamp . timestamp)) [#s(cl-slot-descriptor actions nil (list-of cons) ((:documentation . "A list of actions which this field can perform. Each list element is a cons of string name and function name.")))] [nil] ...)) [#s(cl-slot-descriptor timestamp nil list nil)] #s(hash-table size 65 test eq rehash-size 1.5 rehash-threshold 0.8125 data (timestamp 0 ...)) nil ((:timestamp . timestamp)) [#s(cl-slot-descriptor actions nil (list-of cons) ((:documentation . "A list of actions which this field can perform. Each list element is a cons of string name and function name.")))] [nil] ...) (23019 31212 570825 250000)) #s(#s(eieio--class ebdb-field-timestamp nil (#s(eieio--class ebdb-field "Abstract class for EBDB fields. Subclass this to produce real field types." nil [] #s(hash-table size 65 test eq rehash-size 1.5 rehash-threshold 0.8125 data ( ...)) (ebdb-field-domain ebdb-field-image ebdb-field-relation ebdb-field-timestamp ebdb-field-notes ebdb-field-phone ebdb-field-address ebdb-field-mail ebdb-field-role ebdb-field-name ...) nil [#s(cl-slot-descriptor actions nil (list-of cons) ((:documentation . "A list of actions which this field can perform. Each list element is a cons of string name and function name.")))] [nil] ...)) [#s(cl-slot-descriptor timestamp nil list nil)] #s(hash-table size 65 test eq rehash-size 1.5 rehash-threshold 0.8125 data (timestamp 0 ...)) (ebdb-field-creation-date) ((:timestamp . timestamp)) [#s(cl-slot-descriptor actions nil (list-of cons) ((:documentation . "A list of actions which this field can perform. Each list element is a cons of string name and function name.")))] [nil] ...) (23019 31212 570888 35000)) nil nil nil nil #s(#s(eieio--class ebdb-cache nil nil [#s(cl-slot-descriptor name-string nil string ((:documentation . "The \"canonical\" name for the record, as displayed in the EBDB buffer."))) #s(cl-slot-descriptor alt-names nil (list-of string) ((:documentation . "A list of strings representing all other alternate names for this record."))) #s(cl-slot-descriptor organizations nil list ((:documentation . "A list of strings representing the organizations this record is associated with."))) #s(cl-slot-descriptor mail-aka nil list nil) #s(cl-slot-descriptor mail-canon nil list nil) #s(cl-slot-descriptor sortkey nil string nil) #s(cl-slot-descriptor database nil (list-of ebdb-db) ((:documentation . "The database(s) this record belongs to.")))] #s(hash-table size 65 test eq rehash-size 1.5 rehash-threshold 0.8125 data (name-string 0 alt-names 1 organizations 2 mail-aka 3 mail-canon 4 sortkey 5 database 6 ...)) nil ((:name-string . name-string) (:alt-names . alt-names) (:organizations . organizations) (:mail-aka . mail-aka) (:mail-canon . mail-canon) (:sortkey . sortkey) (:database . database)) [] [] ...) nil nil nil nil nil nil nil) nil ...), #("Elisabeth Verona" 0 16 (org-category "Contacts" fontified nil))

jave commented 6 years ago

i got an error, see above

girzel commented 6 years ago

God I hate EIEIO error message. Give me a bit and I'll put up a new version of the function. Thanks for bearing with me.

girzel commented 6 years ago

Okay, I've updated the function above, will you give that a shot?

jave commented 6 years ago

now I enter an other error state(the error is a bit truncated because terminal issues)

Debugger entered--Lisp error: (wrong-number-of-arguments # 3)
stringp("File: ~/.emacs.d/ebdb" 2 1)
eieio-persistent-convert-list-to-object((ebdb-db-file "File: ~/.emacs.d/ebdb" :object-name "File: ~/.emacs.d/ebdb" :file "ebdb" :uuid (ebdb-field-uuid "ebdb-field-uuid-33044f0" :uuid "552b155a-1b7a-4cb0-a893-$ eieio-persistent-read("~/.emacs.d/ebdb" ebdb-db t)
ebdb-load()
ebdb-search-display(display ((ebdb-field-name "joakim") (organization "joakim") (ebdb-field-mail "joakim") (ebdb-field-notes "joakim") (ebdb-field-user "joakim") (ebdb-field-phone "joakim") (ebdb-field-addres$ ebdb(display "joakim" #)
funcall-interactively(ebdb display "joakim" #)
call-interactively(ebdb record nil)
command-execute(ebdb record)

f(compiled-function (cmd) #<bytecode 0x484e761>)("ebdb")

funcall(#f(compiled-function (cmd) #<bytecode 0x484e761>) "ebdb")
(unwind-protect (funcall action x) (ivy-recursive-restore))
(save-current-buffer (set-buffer (progn (or (and (memq (type-of ivy-last) cl-struct-ivy-state-tags) t) (signal 'wrong-type-argument (list 'ivy-state ivy-last))) (aref ivy-last 13))) (unwind-protect (funcall a$ (prog1 (save-current-buffer (set-buffer (progn (or (and (memq (type-of ivy-last) cl-struct-ivy-state-tags) t) (signal 'wrong-type-argument (list 'ivy-state ivy-last))) (aref ivy-last 13))) (unwind-protect (fu$ (if (eq action 'identity) (funcall action x) (select-window (ivy--get-window ivy-last)) (prog1 (save-current-buffer (set-buffer (progn (or (and (memq (type-of ivy-last) cl-struct-ivy-state-tags) t) (signal 'w$ (let ((collection (progn (or (and (memq (type-of ivy-last) cl-struct-ivy-state-tags) t) (signal 'wrong-type-argument (list 'ivy-state ivy-last))) (aref ivy-last 2))) (x (cond ((and (consp collection) (consp $ (progn (let ((collection (progn (or (and (memq (type-of ivy-last) cl-struct-ivy-state-tags) t) (signal 'wrong-type-argument (list 'ivy-state ivy-last))) (aref ivy-last 2))) (x (cond ((and (consp collection) $ (if action (progn (let ((collection (progn (or (and (memq (type-of ivy-last) cl-struct-ivy-state-tags) t) (signal 'wrong-type-argument (list 'ivy-state ivy-last))) (aref ivy-last 2))) (x (cond ((and (consp c$ (let ((action (ivy--get-action ivy-last))) (if action (progn (let ((collection (progn (or (and (memq (type-of ivy-last) cl-struct-ivy-state-tags) t) (signal 'wrong-type-argument (list 'ivy-state ivy-last))) $ (if ivy-inhibit-action nil (let ((action (ivy--get-action ivy-last))) (if action (progn (let ((collection (progn (or (and (memq (type-of ivy-last) cl-struct-ivy-state-tags) t) (signal 'wrong-type-argument (l$ ivy-call()
(prog1 (unwind-protect (let ((fun (function ivy--minibuffer-setup)) setup-hook) (setq setup-hook (function (lambda nil (remove-hook 'minibuffer-setup-hook setup-hook) (funcall fun)))) (unwind-protect (progn ($ (let ((ivy-recursive-last (and (active-minibuffer-window) ivy-last)) (transformer-fn (plist-get ivy--display-transformers-list (or caller (and (functionp collection) collection)))) (ivy-display-function (if ($ (progn (let ((extra-actions (delete-dups (append (plist-get ivy--actions-list t) (plist-get ivy--actions-list this-command) (plist-get ivy--actions-list caller))))) (if extra-actions (progn (setq action (cond$ (progn (let ((--cl-keys-- --cl-rest--)) (while --cl-keys-- (cond ((memq (car --cl-keys--) '(:predicate :require-match :initial-input :history :preselect :def :keymap :update-fn :sort :action :unwind :re-build$ (let
((predicate (car (cdr (plist-member --cl-rest-- ':predicate)))) (require-match (car (cdr (plist-member --cl-rest-- ':require-match)))) (initial-input (car (cdr (plist-member --cl-rest-- ':initial-input)$ ivy-read("M-x " ("ebdb" "ffap" "ebdb-migrate-from-org-contacts" "list-packages" "shell" "Info-mode" "emacs-lisp-mode" "gnus-alias-select-identity" "org-release-time" "org-freeze-time" "whereiam" "revert-buffe$ counsel-M-x()
funcall-interactively(counsel-M-x)
call-interactively(counsel-M-x nil nil)
command-execute(counsel-M-x)

girzel commented 6 years ago

This is actually a problem with Emacs, not with EBDB, though coincidentally the problem in Emacs is also my fault :(

You'll have to pull Emacs master and rebuild, sorry about that.

jave commented 6 years ago

okay! Ill try it out

jave commented 6 years ago

now i get a really long backtrace. it is so long that i cant copy it here in full, because my terminals hang. here is an excerpt:

Debugger entered--Lisp error: (void-variable field) (car field) (assoc-string (car field) prop-fields) (if (assoc-string (car field) prop-fields) (ebdb-parse (cdr (assoc-string (car-field) prop-fields)) (cdr field)) (let ((val (car-field))) (cond ((equal org-contacts-address-property val) (save-current-buffer (set-buffer address-buffer) (insert (format "%s: %s\n" name (cdr field))))) ((equal org-contacts-birthday-property val) (make-instance 'ebdb-field-anniversary :date (calendar-gregorian-from-absolute (org-time-string-to-absolute (cdr field))) :object-name "birthday")) (t nil)))) (ebdb-record-insert-field record (if (assoc-string (car field) prop-fields) (ebdb-parse (cdr (assoc-string (car-field) prop-fields)) (cdr field)) (let ((val (car-field))) (cond ((equal org-contacts-address-property val) (save-current-buffer (set-buffer address-buffer) (insert (format "%s: %s\n" name (cdr field))))) ((equal org-contacts-birthday-property val) (make-instance 'ebdb-field-anniversary :date (calendar-gregorian-from-absolute (org-time-string-to-absolute (cdr field))) :object-name "birthday")) (t nil)))))

girzel commented 6 years ago

I replaced the earlier code with a new version! You'll have to copy-and-paste the code from the earlier comment again. Sorry if I didn't make that clear.

jave commented 6 years ago

thanks, now the migration starts at least.

Now it stops during migration: Debugger entered--Lisp error: (ebdb-unparseable "joakim")
signal(ebdb-unparseable ("joakim"))
(sorry for the snipping)

my contacts file is likely malformed. It would be nice with some kind of pointer to the original contacts file so i can find out which part is malformed and fix it

girzel commented 6 years ago

The only way I can see this happening is if you had the string "joakim" for a contact's email address. I'll add some more error checking into the process.

Did any of your contacts have a street address? How do they look?

jave commented 6 years ago

I dont think i had any street adresses. the way i used org-contacts devolved into mostly doing isearch in the file. Thats why I wanted to try ebdb!

girzel commented 6 years ago

Okay, cool. Well let me know if there are any other conveniences you're missing. I'm going to add another layer of error checking to the org-contacts migration function, then close this issue.

tumashu commented 6 years ago

cool!!!