gnu-emacs-sources
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: anything is del.icio.us


From: Mark Plaksin
Subject: Re: anything is del.icio.us
Date: Mon, 03 Sep 2007 14:11:03 -0400
User-agent: Gnus/5.110007 (No Gnus v0.7) Emacs/23.0.51 (gnu/linux)

Mark Plaksin <address@hidden> writes:

> Here's an anything source for your del.icio.us posts.

This version notices if you update the post cache
(anything-c-delicious-post-cache) outside of anything-delicious.el.
There's a simple shell script in the comments that you can run once a
day to fetch your posts.  I do want to resolve the problems fetching
posts via the web inside of Emacs.

New action list:
* browse-url
* browse-url-firefox
* w3m-browse-url
* Copy URL to kill ring.

Be sure browse-url-new-window-flag and
browse-url-firefox-new-window-is-tab are set as you want.  I like this:
(setq browse-url-new-window-flag t)
(setq browse-url-firefox-new-window-is-tab t)

This version also fixes a silly bug which made it check for updates once
every 60 days instead of once a day :)

------------------------------------------------------------------------------
;; anything-delicicous.el
;;
;; address@hidden

;; You want to specify a username and password with
;; anything-c-delicious-username and anything-c-delicious-password before
;; loading anything-delicious.el.  This is because there seems to be a bug
;; in url-retrieve-synchronously which messes things up when "401 auth
;; required" is returned.  See
;; http://comments.gmane.org/gmane.emacs.devel/77533

;; If url-retrieve-synchronously is causing your grief, run something like this
;; out of cron once a day and anything-delicious will notice your new posts:
;; 
;; #!/bin/sh
;; 
;; # This gets auth from ~/.netrc.  Sample .netrc contents:
;; # machine api.del.icio.us login YOURUSERNAME password YOURPASSWODR
;; 
;; dir=~/src/backup/delicious
;; out="$dir/backup.`/bin/date +%Y-%m-%d`.xml.gz"
;; curl -n -s https://api.del.icio.us/v1/posts/all | gzip > $out
;; gunzip -dc $out > ~/.emacs.d/anything-delicious-posts

;; TODO:
;; - Better error handling for unexpected status (503, etc) from url-retrieve?
;; - Use/integrate-with/fix delicious-el?

(require 'url-http)

(defvar anything-c-delicious-version "0.3")
  
(defvar anything-c-delicious-hash
  (make-hash-table :test 'equal)
  "Hash table which maps Delicious hashes to full posts.")

(defvar anything-c-delicious-tag-hash
  (make-hash-table :test 'equal)
  "Hash table which maps Delicious tags to posts.
Each tag maps to a list of hashes.  Each hash is a key into
`anything-c-delicious-tag-hash'.")

(defvar anything-c-delicious-username nil
  "Delicious username.")

(defvar anything-c-delicious-password nil
  "Delicious password.")

(defvar anything-c-delicious-api-hostname
  "api.del.icio.us"
  "Delicious API server's FQDN.")

(defvar anything-c-delicious-api
  "/v1/"
  "Path to Delicious API.")

(defvar anything-c-delicious-update
  (concat anything-c-delicious-api "posts/update")
  "Path to fetch timestamp of most recent Delicious post.")

(defvar anything-c-delicious-all-posts
  (concat anything-c-delicious-api "posts/all?")
  "Path to fetch all Delicious posts.")

(defvar anything-c-delicious-post-cache
  (if (boundp 'user-emacs-directory)
      (concat user-emacs-directory "anything-delicious-posts")
    "~/.anything-delicious-posts")
  "Path to Delicious post cache.")

(defvar anything-c-delicious-post-cache-timestamp
  (elt (file-attributes anything-c-delicious-post-cache) 5)
  "Last noted modification time of cache file.")

(defvar anything-c-delicious-update-time
  nil
  "Time of last update of all Delicious posts in internal Emacs time format.")

(defvar anything-c-delicious-sort-function
  'anything-c-delicious-sort-recent-first
  "Function used to sort posts.")

(defvar anything-c-delicious-last-check
  (current-time)
  "Last time anything-c-delicious checked for new posts.")

(defvar anything-c-delicious-check-interval
  1440
  "Minutes to wait between checks for new Delicious posts.
Defaults to one day.")

(defvar anything-c-delicious-user-agent
  (concat "User-Agent: anything-delicious/" anything-c-delicious-version)
  "User-Agent to send to Delicious.
Delicious asks each application to use its own User-Agent")

(defvar anything-c-delicious-debug
  nil
  "If non-nil, do not delete buffers created by url-retrieve.")

;; stolen from delicioapi.el
(defun anything-c-delicious-auth ()
  "Return the authorization string.
It is determined using `anything-c-delicious-username' and
`anything-c-delicious-password'."
  (base64-encode-string
   (format "%s:%s" anything-c-delicious-username 
anything-c-delicious-password)))

;; stolen from delicioapi.el
(defun anything-c-delicious-register-auth ()
  "Register delicious auth information."
  (let ((auth-info (list (format "%s:443" anything-c-delicious-api-hostname)
                         (cons "del.icio.us API" (anything-c-delicious-auth)))))
    (add-to-list 'url-http-real-basic-auth-storage auth-info)))

;; Until the bug in (or my cluefullness about) url-retrieve is fixed.
(if (and anything-c-delicious-username
         anything-c-delicious-password)
    (anything-c-delicious-register-auth))

;; There must be a function that does this already!  The ones I found and tried
;; didn't work.
(defun anything-c-delicious-encode-time (string)
  "Convert STRING to internal Emacs time.
Sample STRING: 2007-08-21T14:42:20Z"
  (apply 'encode-time
         (mapcar 'string-to-number
                 (cdr (reverse (split-string string "[-T:Z]"))))))

;; Mostly stolen from delicioapi.el.  I changed it so it works when the url
;; is a string or an array.  It still feels fragile though.
(defadvice url-http-user-agent-string 
  (after anything-c-delicious-user-agent activate)
  "If talking to api.del.icio.us override User-Agent."
  (if (or
       (and (stringp url)
            (string-match "https*://api.del.icio.us" url))
       (and (not (stringp url))
            (string= (aref url 4) "api.del.icio.us")))
      (setq ad-return-value
            (concat "User-Agent: " anything-c-delicious-user-agent "\r\n"))))

(defun anything-c-delicious-maybe-update-posts ()
  "Maybe fetch new Delicious posts.
If `anything-c-delicious-check-interval' has passed, posts have
not been read into hashes, fetch posts."
  ;; If the hash is empty or the timestamp on the cache file has changed,
  ;; try to read posts in from cache.
  (if (or (= (hash-table-count anything-c-delicious-hash) 0)
          (and anything-c-delicious-post-cache-timestamp
               (< (float-time anything-c-delicious-post-cache-timestamp)
                  (float-time (elt (file-attributes 
anything-c-delicious-post-cache) 5)))))
      (anything-c-delicious-get-posts t)
    ;; Otherwise, if the check interval has passed, check for updates    
    (if (not (time-less-p
              (current-time)
              (time-add anything-c-delicious-last-check
                        (seconds-to-time
                         (* anything-c-delicious-check-interval 60)))))
        (save-excursion
          (setq anything-c-delicious-last-check (current-time))
          (let* ((update-url
                  (concat "https://";
                          anything-c-delicious-api-hostname
                          anything-c-delicious-update))
                 (buffer (progn
                           (sit-for 1)
                           ;; TODO: This is ugly error handling!
                           (condition-case nil
                               (url-retrieve-synchronously update-url)
                             (error nil)))))
            (if buffer
                (progn
                  ;; 8/30/2007: Update URL returns this:
                  ;; ?xml version='1.0' standalone='yes'?>
                  ;; <update time="2007-08-29T11:47:52Z" />
                  (set-buffer buffer)
                  (goto-char (point-min))
                  (search-forward "<update " nil t)
                  (if (or (not anything-c-delicious-update-time)
                          (time-less-p anything-c-delicious-update-time
                                       (anything-c-delicious-encode-time
                                        (anything-c-delicious-read-post-field 
"time"))))
                      (anything-c-delicious-get-posts)
                    ;; If there's no update and the hashes are not built,
                    ;; try to read posts from cache.
                    (if (= (hash-table-count anything-c-delicious-hash)
                           0)
                        (anything-c-delicious-get-posts t)))
                  (if (not anything-c-delicious-debug)
                           (kill-buffer buffer)))))))))

(defun anything-c-delicious-get-posts (&optional fromcache)
  "Fetch all delicious posts and create tag tables.
If FROMCACHE is non-nil, try to read posts from
`anything-c-delicious-post-cache'.  If that fails, fetch them via
the web."
  (clrhash anything-c-delicious-hash)
  (clrhash anything-c-delicious-tag-hash)
  (save-excursion
    (let* ((update-url
            (concat "https://";
                    anything-c-delicious-api-hostname
                    anything-c-delicious-all-posts))
           (buffer))
      ;; If FROMCACHE is non-nil, try to read the posts from the cache.
      (if fromcache
          (progn
            (setq buffer (get-buffer-create " *delicious post cache*"))
            (set-buffer buffer)
            (condition-case nil
                (insert-file-contents anything-c-delicious-post-cache)
              ;; If reading from cache fails, read from the web and be sure to
              ;; cache the result
              (error
               (setq buffer nil
                     fromcache nil)))))
      (if (not buffer)
          (progn
            (message "Getting Delicious posts via the web.  Hang on.")
            (sit-for 1)
            ;; TODO: This is ugly error handling!
            (setq buffer (condition-case nil
                             (url-retrieve-synchronously update-url)
                           (error nil)))))
      (if buffer
          (let (href desc extd hash tags time temp)
            (set-buffer buffer)
            (goto-char (point-min))
            (search-forward "<posts " nil t)
            (setq anything-c-delicious-update-time
                  (anything-c-delicious-encode-time
                   (anything-c-delicious-read-post-field "update")))
            (while (search-forward "<post " nil t)
              (setq href (anything-c-delicious-read-post-field "href")
                    desc (anything-c-delicious-read-post-field "description")
                    extd (anything-c-delicious-read-post-field "extended")
                    hash (anything-c-delicious-read-post-field "hash")
                    time (anything-c-delicious-encode-time 
(anything-c-delicious-read-post-field "time"))
                    tags (split-string (anything-c-delicious-read-post-field 
"tag") " "))
              (puthash hash (list tags href desc extd time) 
anything-c-delicious-hash )
              (dolist (tag tags)
                (puthash tag
                         (cons hash (gethash tag anything-c-delicious-tag-hash))
                         anything-c-delicious-tag-hash)))
            (if (not fromcache)
                (write-file anything-c-delicious-post-cache))
            (setq anything-c-delicious-post-cache-timestamp
                  (elt (file-attributes anything-c-delicious-post-cache) 5))
            (if (not anything-c-delicious-debug)
                (kill-buffer buffer)))))))

(defun anything-c-delicious-read-post-field (field)
  "Return the value of FIELD in the current post."
  (save-excursion
    (re-search-forward (concat field "=\"\\(.*?\\)\""))
    (match-string 1)))

;; How slow is this?  It's pretty speedy for me and my ~1300 posts.
(defun anything-c-delicious-string-search (string)
  "Return a list of delicious posts matching STRING.
Searches delicious post tags and contents for STRING.  Returns a
list of posts."
  (let ((result))
    ;; search tags first
    (maphash '(lambda (tag hash-list)
                (if (string-match string tag)
                    (dolist (hash hash-list)
                            (if (not (member hash result))
                                (add-to-list 'result hash)))))
             anything-c-delicious-tag-hash)
    ;; then search everything else
    (maphash '(lambda (key value)
                (dolist (item (cdr value))
                  (if (and
                       (and (stringp item)
                            (string-match string item))
                       (not (member key result)))
                      (add-to-list 'result key))))
             anything-c-delicious-hash)
    (if (functionp anything-c-delicious-sort-function)
        (funcall anything-c-delicious-sort-function result)
      result)))

(defun anything-c-delicious-sort-recent-first (list)
  "Sort posts in LIST with most recent posts first."
  (sort list
        '(lambda (a b)
           (time-less-p
            (elt (gethash b anything-c-delicious-hash) 4)
            (elt (gethash a anything-c-delicious-hash) 4)))))

(defun anything-c-delicious-filtered-candidate-transformer (list source)
  "Transform a list of delicious posts into (DISPLAY . REAL) pairs."
  (mapcar
   (lambda (candidate)
     ;; candidate is simply a hash
     (let ((post (gethash candidate anything-c-delicious-hash)))
       ;; elt 2 is desc, elt 1 is URL
       `(,(elt post 2) .
         ,(elt post 1))))
   list))

(defvar anything-c-delicious-source
  '((name . "delicious")
    (init . anything-c-delicious-maybe-update-posts)
    (candidates
     . (lambda ()
         (anything-c-delicious-string-search anything-pattern)))
    (match
     . ((lambda (candidate)
          (string-match anything-pattern (prin1-to-string (gethash candidate 
anything-c-delicious-hash) t)))))
    (filtered-candidate-transformer
     . (lambda (list source)
         (anything-c-delicious-filtered-candidate-transformer list source)))
    (action .
            (("browse-url" . browse-url)
             ("browse-url-firefox" . browse-url-firefox)
             ("w3m-browse-url" . w3m-browse-url)
             ("Copy URL to kill ring" .
              (lambda (url)
                (kill-new url)))))
    (delayed)
    ;; Needed?
    (volatile)
    (requires-pattern . 3))
  "Search your Delicious posts.
It searches each part of every post--the URL, the tags and both descriptions.")





reply via email to

[Prev in Thread] Current Thread [Next in Thread]