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

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

anything is del.icio.us


From: Mark Plaksin
Subject: anything is del.icio.us
Date: Sat, 01 Sep 2007 15:15:58 -0400
User-agent: Gnus/5.110007 (No Gnus v0.7) Emacs/23.0.51 (gnu/linux)

Here's an anything source for your del.icio.us posts.  It displays the
posts which contain anything-pattern in any part of your post (URL,
tags, and both the short and extended descriptions).  The most recent
matching posts appear first.

The first time you run anything after adding this source it will
download all your posts.  Be patient.  That part should probably be
asynchronous--maybe in v0.4 :) It caches your posts in a file and checks
for new posts once a day.  If there are new posts it downloads all your
posts again and updates the cache--in theory anyhow!  I haven't tested
that last part yet.

Because of a bug in me or in url-retrieve-synchronously, it works best
if you set anything-c-delicious-username and
anything-c-delicious-password before loading the library.

It should probably use delicious-el [1].  But delicious-el needs to be
updated to match del.icio.us' new API and I was too excited about
getting anything working with del.icio.us to do that.

Footnotes: 
[1]  http://www.wjsullivan.net/delicious-el.html

------------------------------------------------------------------------------
;; anything-delicious.el
;; Version 0.1 
;; address@hidden
;; 
;; You want to specify a username and password with
;; anything-c-delicious-username and anything-c-delicious-username before
;; loading anything-delicious.el.  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

;; TODO:
;; - Better error handling for unexpected status (503, etc) from url-retrieve?
;; - If anything-c-delicious-check-interval is nil, never check for updates
;;   via del.icio.us.  Instead check to see whether the cache file has changed.
;; - Use/integrate-with/fix delicious-el?
;; - Add some action choices (sometimes you might want to open in emacs-w3m or
;;   firefox or copy URL to kill-ring, etc)

(require 'url-http)

(defvar anything-c-delicious-version "0.1")
  
(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
  (concat user-emacs-directory "anything-delicious-posts")
  "Path to Delicious post cache.")

(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
  86400
  "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, try to read posts in from cache
  (if (= (hash-table-count anything-c-delicious-hash) 0)
      (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))
            (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)
    (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]