Add 2012 to FSF copyright years for Emacs files
[bpt/emacs.git] / lisp / net / newst-backend.el
index f0174c5..c78249c 100644 (file)
@@ -1,13 +1,13 @@
 ;;; newst-backend.el --- Retrieval backend for newsticker.
 
-;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008
-;;   Free Software Foundation, Inc.
+;; Copyright (C) 2003-2012  Free Software Foundation, Inc.
 
 ;; Author:      Ulf Jasper <ulf.jasper@web.de>
 ;; Filename:    newst-backend.el
 ;; URL:         http://www.nongnu.org/newsticker
 ;; Keywords:    News, RSS, Atom
-;; Time-stamp:  "7. Juli 2008, 19:20:10 (ulf)"
+;; Time-stamp:  "13. Mai 2011, 20:47:05 (ulf)"
+;; Package:     newsticker
 
 ;; ======================================================================
 
@@ -79,13 +79,11 @@ considered to be running if the newsticker timer list is not empty."
     nil
     3600)
     ("Freshmeat.net"
-    "http://freshmeat.net/backend/fm.rdf")
+    "http://freshmeat.net/index.atom")
     ("Kuro5hin.org"
     "http://www.kuro5hin.org/backend.rdf")
     ("LWN (Linux Weekly News)"
     "http://lwn.net/headlines/rss")
-    ("NewsForge"
-    "http://newsforge.com/index.rss")
     ("NY Times: Technology"
     "http://partners.userland.com/nytRss/technology.xml")
     ("NY Times"
@@ -423,11 +421,12 @@ headline after it has been retrieved for the first time."
   "Name of the newsticker cache file."
   :type 'string
   :group 'newsticker-miscellaneous)
+(make-obsolete 'newsticker-cache-filename 'newsticker-dir "23.1")
 
-(defcustom newsticker-imagecache-dirname
-  "~/.newsticker-images"
-  "Name of the directory where newsticker stores cached images."
-  :type 'string
+(defcustom newsticker-dir
+  (locate-user-emacs-file "newsticker/" ".newsticker/")
+  "Directory where newsticker saves data."
+  :type 'directory
   :group 'newsticker-miscellaneous)
 
 ;; debugging
@@ -443,10 +442,15 @@ buffers *newsticker-wget-<feed>* will not be closed."
 ;; ======================================================================
 ;;; Compatibility section, XEmacs, Emacs
 ;; ======================================================================
+
+;; FIXME It is bad practice to define compat functions with such generic names.
+
+;; This is not needed in Emacs >= 22.1.
 (unless (fboundp 'time-add)
   (require 'time-date);;FIXME
   (defun time-add (t1 t2)
-    (seconds-to-time (+ (time-to-seconds t1) (time-to-seconds t2)))))
+    (with-no-warnings ; don't warn about obsolete time-to-seconds in 23.2
+      (seconds-to-time (+ (time-to-seconds t1) (time-to-seconds t2))))))
 
 (unless (fboundp 'match-string-no-properties)
   (defalias 'match-string-no-properties 'match-string))
@@ -629,16 +633,7 @@ Run `newsticker-start-hook' if newsticker was not running already."
   (let ((running (newsticker-running-p)))
     ;; read old cache if it exists and newsticker is not running
     (unless running
-      (let ((coding-system-for-read 'utf-8))
-        (when (file-exists-p newsticker-cache-filename)
-          (with-temp-buffer
-            (insert-file-contents newsticker-cache-filename)
-            (goto-char (point-min))
-            (condition-case nil
-              (setq newsticker--cache (read (current-buffer)))
-            (error
-             (message "Error while reading newsticker cache file!")
-             (setq newsticker--cache nil)))))))
+      (newsticker--cache-read))
     ;; start retrieval timers -- one timer for each feed
     (dolist (feed (append newsticker-url-list-defaults newsticker-url-list))
       (newsticker--start-feed feed))
@@ -660,7 +655,7 @@ Delete the stopped name/timer pair from `newsticker--retrieval-timer-list'."
 Cancel the timers for display and retrieval.  Run `newsticker-stop-hook'
 if newsticker has been running."
   (interactive)
-  (newsticker--cache-update t)
+  (newsticker--cache-save)
   (when (fboundp 'newsticker-stop-ticker) ; silence compiler warnings
     (newsticker-stop-ticker))
   (when (newsticker-running-p)
@@ -722,6 +717,7 @@ If URL is nil it is searched at point."
 (defun newsticker-customize ()
   "Open the newsticker customization group."
   (interactive)
+  (delete-other-windows)
   (customize-group "newsticker"))
 
 ;; ======================================================================
@@ -731,8 +727,7 @@ If URL is nil it is searched at point."
   "Get news for the site FEED-NAME by calling FUNCTION.
 See `newsticker-get-news'."
   (let ((buffername (concat " *newsticker-funcall-" feed-name "*")))
-    (save-excursion
-      (set-buffer (get-buffer-create buffername))
+    (with-current-buffer (get-buffer-create buffername)
       (erase-buffer)
       (insert (string-to-multibyte (funcall function feed-name)))
       (newsticker--sentinel-work nil t feed-name function
@@ -743,7 +738,7 @@ See `newsticker-get-news'."
 See `newsticker-get-news'."
   (let ((coding-system-for-read 'no-conversion))
     (condition-case error-data
-        (url-retrieve url 'newsticker--get-news-by-url-callback 
+        (url-retrieve url 'newsticker--get-news-by-url-callback
                       (list feed-name))
           (error (message "Error retrieving news from %s: %s" feed-name
                           error-data))))
@@ -782,8 +777,7 @@ from."
 WGET-ARGUMENTS is a list of arguments for wget.
 See `newsticker-get-news'."
   (let ((buffername (concat " *newsticker-wget-" feed-name "*")))
-    (save-excursion
-      (set-buffer (get-buffer-create buffername))
+    (with-current-buffer (get-buffer-create buffername)
       (erase-buffer)
       ;; throw an error if there is an old wget-process around
       (if (get-process feed-name)
@@ -1035,7 +1029,8 @@ Argument BUFFER is the buffer of the retrieval process."
         (setq newsticker--latest-update-time (current-time))
         (when something-was-added
           ;; FIXME: should we care about removed items as well?
-          (newsticker--cache-update)
+          (newsticker--cache-save-feed
+           (newsticker--cache-get-feed name-symbol))
           (when (fboundp 'newsticker--buffer-set-uptodate) ;silence
                                                            ;compiler
                                                            ;warnings
@@ -1137,7 +1132,7 @@ system time at which the data have been retrieved.  TOPNODE
 contains the feed data as returned by the xml parser.
 
 For the Atom 1.0 specification see
-http://www.atompub.org/2005/08/17/draft-ietf-atompub-format-11.html"
+URL `http://www.atompub.org/2005/08/17/draft-ietf-atompub-format-11.html'"
   (newsticker--debug-msg "Parsing Atom 1.0 feed %s" name)
   (let (new-feed new-item)
     (setq new-feed (newsticker--parse-generic-feed
@@ -1191,8 +1186,8 @@ http://www.atompub.org/2005/08/17/draft-ietf-atompub-format-11.html"
 Return value as well as arguments NAME, TIME, and TOPNODE are the
 same as in `newsticker--parse-atom-1.0'.
 
-For the RSS 0.91 specification see http://backend.userland.com/rss091 or
-http://my.netscape.com/publish/formats/rss-spec-0.91.html."
+For the RSS 0.91 specification see URL `http://backend.userland.com/rss091'
+or URL `http://my.netscape.com/publish/formats/rss-spec-0.91.html'."
   (newsticker--debug-msg "Parsing RSS 0.91 feed %s" name)
   (let* ((channelnode (car (xml-get-children topnode 'channel)))
          (pub-date (newsticker--decode-rfc822-date
@@ -1245,7 +1240,7 @@ http://my.netscape.com/publish/formats/rss-spec-0.91.html."
 Return value as well as arguments NAME, TIME, and TOPNODE are the
 same as in `newsticker--parse-atom-1.0'.
 
-For the RSS 0.92 specification see http://backend.userland.com/rss092."
+For the RSS 0.92 specification see URL `http://backend.userland.com/rss092'."
   (newsticker--debug-msg "Parsing RSS 0.92 feed %s" name)
   (let* ((channelnode (car (xml-get-children topnode 'channel)))
          (pub-date (newsticker--decode-rfc822-date
@@ -1298,7 +1293,7 @@ For the RSS 0.92 specification see http://backend.userland.com/rss092."
 Return value as well as arguments NAME, TIME, and TOPNODE are the
 same as in `newsticker--parse-atom-1.0'.
 
-For the RSS 1.0 specification see http://web.resource.org/rss/1.0/spec."
+For the RSS 1.0 specification see URL `http://web.resource.org/rss/1.0/spec'."
   (newsticker--debug-msg "Parsing RSS 1.0 feed %s" name)
   (let* ((channelnode (car (xml-get-children topnode 'channel)))
          is-new-feed has-new-items)
@@ -1349,7 +1344,7 @@ For the RSS 1.0 specification see http://web.resource.org/rss/1.0/spec."
 Return value as well as arguments NAME, TIME, and TOPNODE are the
 same as in `newsticker--parse-atom-1.0'.
 
-For the RSS 2.0 specification see http://blogs.law.harvard.edu/tech/rss."
+For the RSS 2.0 specification see URL `http://blogs.law.harvard.edu/tech/rss'."
   (newsticker--debug-msg "Parsing RSS 2.0 feed %s" name)
   (let* ((channelnode (car (xml-get-children topnode 'channel)))
          is-new-feed has-new-items)
@@ -1413,9 +1408,9 @@ description, link, and extra elements resp."
         (position 0)
         (something-was-added nil))
     ;; decode numeric entities
-    (setq title (newsticker--decode-numeric-entities title))
-    (setq desc  (newsticker--decode-numeric-entities desc))
-    (setq link  (newsticker--decode-numeric-entities link))
+    (setq title (xml-substitute-numeric-entities title))
+    (setq desc  (xml-substitute-numeric-entities desc))
+    (setq link  (xml-substitute-numeric-entities link))
     ;; remove whitespace from title, desc, and link
     (setq title (newsticker--remove-whitespace title))
     (setq desc (newsticker--remove-whitespace desc))
@@ -1467,10 +1462,10 @@ argument, which is one of the items in ITEMLIST."
             (when (or (> (length title) 0)
                       (> (length desc) 0))
               ;; decode numeric entities
-              (setq title (newsticker--decode-numeric-entities title))
+              (setq title (xml-substitute-numeric-entities title))
               (when desc
-                (setq desc  (newsticker--decode-numeric-entities desc)))
-              (setq link (newsticker--decode-numeric-entities link))
+                (setq desc  (xml-substitute-numeric-entities desc)))
+              (setq link (xml-substitute-numeric-entities link))
               ;; remove whitespace from title, desc, and link
               (setq title (newsticker--remove-whitespace title))
               (setq desc (newsticker--remove-whitespace desc))
@@ -1522,24 +1517,6 @@ argument, which is one of the items in ITEMLIST."
 ;; ======================================================================
 ;;; Misc
 ;; ======================================================================
-(defun newsticker--decode-numeric-entities (string)
-  "Decode SGML numeric entities by their respective utf characters.
-This function replaces numeric entities in the input STRING and
-returns the modified string.  For example \"&#42;\" gets replaced
-by \"*\"."
-  (if (and string (stringp string))
-      (let ((start 0))
-        (while (string-match "&#\\([0-9]+\\);" string start)
-          (condition-case nil
-              (setq string (replace-match
-                            (string (read (substring string
-                                                     (match-beginning 1)
-                                                     (match-end 1))))
-                            nil nil string))
-            (error nil))
-          (setq start (1+ (match-beginning 0))))
-        string)
-    nil))
 
 (defun newsticker--remove-whitespace (string)
   "Remove leading and trailing whitespace from STRING."
@@ -1733,11 +1710,14 @@ Checks list of active processes against list of newsticker processes."
 ;; ======================================================================
 ;;; Images
 ;; ======================================================================
+(defun newsticker--images-dir ()
+  "Return directory where feed images are saved."
+  (concat newsticker-dir "/images"))
+
 (defun newsticker--image-get (feed-name url)
   "Get image of the news site FEED-NAME from URL.
 If the image has been downloaded in the last 24h do nothing."
-  (let ((image-name (concat newsticker-imagecache-dirname "/"
-                            feed-name)))
+  (let ((image-name (concat (newsticker--images-dir) feed-name)))
     (if (and (file-exists-p image-name)
              (time-less-p (current-time)
                           (time-add (nth 5 (file-attributes image-name))
@@ -1757,8 +1737,7 @@ If the image has been downloaded in the last 24h do nothing."
                         feed-name)))
              (wget-arguments (or (car (cdr (cdr (cdr (cdr item)))))
                                  newsticker-wget-arguments)))
-        (save-excursion
-          (set-buffer (get-buffer-create buffername))
+        (with-current-buffer (get-buffer-create buffername)
           (erase-buffer)
           ;; throw an error if there is an old wget-process around
           (if (get-process feed-name)
@@ -1786,14 +1765,12 @@ If the image has been downloaded in the last 24h do nothing."
                  feed-name)
         (throw 'oops nil))
       (let (image-name)
-        (save-excursion
-          (set-buffer (process-buffer process))
-          (setq image-name (concat newsticker-imagecache-dirname "/"
-                                   feed-name))
+        (with-current-buffer (process-buffer process)
+          (setq image-name (concat (newsticker--images-dir) feed-name))
           (set-buffer-file-coding-system 'no-conversion)
           ;; make sure the cache dir exists
-          (unless (file-directory-p newsticker-imagecache-dirname)
-            (make-directory newsticker-imagecache-dirname))
+          (unless (file-directory-p (newsticker--images-dir))
+            (make-directory (newsticker--images-dir)))
           ;; write and close buffer
           (let ((require-final-newline nil)
                 (backup-inhibited t)
@@ -1802,8 +1779,6 @@ If the image has been downloaded in the last 24h do nothing."
           (set-buffer-modified-p nil)
           (kill-buffer (current-buffer)))))))
 
-
-
 (defun newsticker--insert-image (img string)
   "Insert IMG with STRING at point."
   (insert-image img string))
@@ -1936,12 +1911,12 @@ other properties are ignored."
                                                     newsticker-desc-comp-max))
                                             (string= (substring
                                                       (newsticker--desc anitem)
-                                                      0 
+                                                      0
                                                       newsticker-desc-comp-max)
                                                      desc)
                                           (string= (newsticker--desc anitem)
                                                    desc)))))))
-                       ;;(newsticker--debug-msg "Found %s guid=%s" 
+                       ;;(newsticker--debug-msg "Found %s guid=%s"
                        ;; (newsticker--title anitem)
                        ;;                     (newsticker--guid anitem))
                        (throw 'found anitem)))
@@ -1970,7 +1945,7 @@ which the item got."
     (if item
         ;; does exist already -- change age, update time and position
         (progn
-          ;;(newsticker--debug-msg "Updating item %s %s %s %s %s -> %s %s 
+          ;;(newsticker--debug-msg "Updating item %s %s %s %s %s -> %s %s
           ;;                   (guid %s -> %s)"
           ;;                   feed-name-symbol title link time age
           ;;                 updated-time updated-age
@@ -2094,9 +2069,7 @@ well."
                  (throw 'result t)))))
     (< (or (newsticker--pos item1) 0) (or (newsticker--pos item2) 0))))
 
-
-
-(defun newsticker--cache-save ()
+(defun newsticker--cache-save-version1 ()
   "Update and save newsticker cache file."
   (interactive)
   (newsticker--cache-update t))
@@ -2105,14 +2078,17 @@ well."
   "Update newsticker cache file.
 If optional argument SAVE is not nil the cache file is saved to disk."
   (save-excursion
-    (let ((coding-system-for-write 'utf-8))
-      (with-temp-buffer
+    (unless (file-directory-p newsticker-dir)
+      (make-directory newsticker-dir t))
+    (let ((coding-system-for-write 'utf-8)
+          (buf (find-file-noselect newsticker-cache-filename)))
+      (when buf
+        (set-buffer buf)
         (setq buffer-undo-list t)
         (erase-buffer)
         (insert ";; -*- coding: utf-8 -*-\n")
         (insert (prin1-to-string newsticker--cache))
         (when save
-          (set-visited-file-name newsticker-cache-filename)
           (save-buffer))))))
 
 (defun newsticker--cache-get-feed (feed)
@@ -2120,6 +2096,69 @@ If optional argument SAVE is not nil the cache file is saved to disk."
 FEED is a symbol!"
   (assoc feed newsticker--cache))
 
+(defun newsticker--cache-dir ()
+  "Return directory for saving cache data."
+  (concat newsticker-dir "/feeds"))
+
+(defun newsticker--cache-save ()
+    "Save cache data for all feeds."
+    (unless (file-directory-p newsticker-dir)
+      (make-directory newsticker-dir t))
+    (mapc 'newsticker--cache-save-feed newsticker--cache)
+    nil)
+
+(defun newsticker--cache-save-feed (feed)
+  "Save cache data for FEED."
+  (let ((dir (concat (newsticker--cache-dir) "/" (symbol-name (car feed)))))
+    (unless (file-directory-p dir)
+      (make-directory dir t))
+    (let ((coding-system-for-write 'utf-8))
+      (with-temp-file (concat dir "/data")
+        (insert ";; -*- coding: utf-8 -*-\n")
+        (insert (prin1-to-string (cdr feed)))))))
+
+(defun newsticker--cache-read-version1 ()
+  "Read version1 cache data."
+  (let ((coding-system-for-read 'utf-8))
+    (when (file-exists-p newsticker-cache-filename)
+      (with-temp-buffer
+        (insert-file-contents newsticker-cache-filename)
+        (goto-char (point-min))
+        (condition-case nil
+            (setq newsticker--cache (read (current-buffer)))
+          (error
+           (message "Error while reading newsticker cache file!")
+           (setq newsticker--cache nil)))))))
+
+(defun newsticker--cache-read ()
+  "Read cache data."
+  (setq newsticker--cache nil)
+  (if (file-exists-p newsticker-cache-filename)
+      (progn
+        (when (y-or-n-p "Old newsticker cache file exists.  Read it? ")
+          (newsticker--cache-read-version1))
+        (when (y-or-n-p (format "Delete old newsticker cache file? "))
+          (delete-file newsticker-cache-filename)))
+    (mapc (lambda (f)
+            (newsticker--cache-read-feed (car f)))
+          (append newsticker-url-list-defaults newsticker-url-list))))
+
+(defun newsticker--cache-read-feed (feed-name)
+  "Read cache data for feed named FEED-NAME."
+  (let ((file-name (concat (newsticker--cache-dir) "/" feed-name "/data"))
+        (coding-system-for-read 'utf-8))
+    (when (file-exists-p file-name)
+      (with-temp-buffer
+        (insert-file-contents file-name)
+        (goto-char (point-min))
+        (condition-case nil
+            (add-to-list 'newsticker--cache (cons (intern feed-name)
+                                                  (read (current-buffer))))
+          (error
+           (message "Error while reading newsticker cache file %s!"
+                    file-name)
+           (setq newsticker--cache nil)))))))
+
 ;; ======================================================================
 ;;; Statistics
 ;; ======================================================================
@@ -2225,23 +2264,30 @@ for an entry that matches FEED and ITEM."
   "Actually compare ITEM against the pattern-LIST.
 LIST must be an element of `newsticker-auto-mark-filter-list'."
   (mapc (lambda (pattern)
-          (let ((age    (nth 0 pattern))
-                (place  (nth 1 pattern))
+          (let ((place  (nth 1 pattern))
                 (regexp (nth 2 pattern))
                 (title (newsticker--title item))
                 (desc  (newsticker--desc item)))
             (when (or (eq place 'title) (eq place 'all))
               (when (and title (string-match regexp title))
-                (newsticker--debug-msg "Auto-marking as %s: `%s'"
-                                       age (newsticker--title item))
-                (setcar (nthcdr 4 item) age)))
+                (newsticker--process-auto-mark-filter-match item pattern)))
             (when (or (eq place 'description) (eq place 'all))
               (when (and desc (string-match regexp desc))
-                (newsticker--debug-msg "Auto-marking as %s: `%s'"
-                                       age (newsticker--title item))
-                (setcar (nthcdr 4 item) age)))))
+                (newsticker--process-auto-mark-filter-match item pattern)))))
         list))
 
+(defun newsticker--process-auto-mark-filter-match (item pattern)
+  "Process ITEM that matches an auto-mark-filter PATTERN."
+  (let ((age (nth 0 pattern))
+        (place  (nth 1 pattern))
+        (regexp (nth 2 pattern)))
+    (newsticker--debug-msg "Auto-mark-filter %s matches `%s'"
+                           pattern (newsticker--title item))
+    (setcar (nthcdr 4 item) age)
+    (nconc (newsticker--extra item)
+           (list (list 'newsticker-auto-mark nil
+                       (format "age=%s, title/desc=%s, regexp=%s"
+                               age place regexp))))))
 
 ;; ======================================================================
 ;;; Hook samples
@@ -2311,7 +2357,6 @@ This function is suited for adding it to `newsticker-new-item-functions'."
           "</title><description>" (format "Or maybe it is %d" (random 10000))
           "</description></item></channel></rss>"))
 
-(provide 'newsticker-backend)
+(provide 'newst-backend)
 
-;; arch-tag: 0e37b658-56e9-49ab-90f9-f2df57e1a659
-;;; newsticker-backend.el ends here
+;;; newst-backend.el ends here