nneething.el 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. ;;; nneething.el --- arbitrary file access for Gnus
  2. ;; Copyright (C) 1995-2012 Free Software Foundation, Inc.
  3. ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
  4. ;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
  5. ;; Keywords: news, mail
  6. ;; This file is part of GNU Emacs.
  7. ;; GNU Emacs is free software: you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation, either version 3 of the License, or
  10. ;; (at your option) any later version.
  11. ;; GNU Emacs is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;;; Code:
  19. (eval-when-compile (require 'cl))
  20. (require 'mailcap)
  21. (require 'nnheader)
  22. (require 'nnmail)
  23. (require 'nnoo)
  24. (require 'gnus-util)
  25. (nnoo-declare nneething)
  26. (defvoo nneething-map-file-directory
  27. (nnheader-concat gnus-directory ".nneething/")
  28. "Where nneething stores the map files.")
  29. (defvoo nneething-map-file ".nneething"
  30. "Name of the map files.")
  31. (defvoo nneething-exclude-files nil
  32. "Regexp saying what files to exclude from the group.
  33. If this variable is nil, no files will be excluded.")
  34. (defvoo nneething-include-files nil
  35. "Regexp saying what files to include in the group.
  36. If this variable is non-nil, only files matching this regexp will be
  37. included.")
  38. ;;; Internal variables.
  39. (defconst nneething-version "nneething 1.0"
  40. "nneething version.")
  41. (defvoo nneething-current-directory nil
  42. "Current news group directory.")
  43. (defvoo nneething-status-string "")
  44. (defvoo nneething-work-buffer " *nneething work*")
  45. (defvoo nneething-group nil)
  46. (defvoo nneething-map nil)
  47. (defvoo nneething-read-only nil)
  48. (defvoo nneething-active nil)
  49. (defvoo nneething-address nil)
  50. ;;; Interface functions.
  51. (nnoo-define-basics nneething)
  52. (deffoo nneething-retrieve-headers (articles &optional group server fetch-old)
  53. (nneething-possibly-change-directory group)
  54. (with-current-buffer nntp-server-buffer
  55. (erase-buffer)
  56. (let* ((number (length articles))
  57. (count 0)
  58. (large (and (numberp nnmail-large-newsgroup)
  59. (> number nnmail-large-newsgroup)))
  60. article file)
  61. (if (stringp (car articles))
  62. 'headers
  63. (while (setq article (pop articles))
  64. (setq file (nneething-file-name article))
  65. (when (and (file-exists-p file)
  66. (or (file-directory-p file)
  67. (not (zerop (nnheader-file-size file)))))
  68. (insert (format "221 %d Article retrieved.\n" article))
  69. (nneething-insert-head file)
  70. (insert ".\n"))
  71. (incf count)
  72. (and large
  73. (zerop (% count 20))
  74. (nnheader-message 5 "nneething: Receiving headers... %d%%"
  75. (/ (* count 100) number))))
  76. (when large
  77. (nnheader-message 5 "nneething: Receiving headers...done"))
  78. (nnheader-fold-continuation-lines)
  79. 'headers))))
  80. (deffoo nneething-request-article (id &optional group server buffer)
  81. (nneething-possibly-change-directory group)
  82. (let ((file (unless (stringp id)
  83. (nneething-file-name id)))
  84. (nntp-server-buffer (or buffer nntp-server-buffer)))
  85. (and (stringp file) ; We did not request by Message-ID.
  86. (file-exists-p file) ; The file exists.
  87. (not (file-directory-p file)) ; It's not a dir.
  88. (save-excursion
  89. (let ((nnmail-file-coding-system 'binary))
  90. (nnmail-find-file file)) ; Insert the file in the nntp buf.
  91. (unless (nnheader-article-p) ; Either it's a real article...
  92. (let ((type
  93. (unless (file-directory-p file)
  94. (or (cdr (assoc (concat "." (file-name-extension file))
  95. mailcap-mime-extensions))
  96. "text/plain")))
  97. (charset
  98. (mm-detect-mime-charset-region (point-min) (point-max)))
  99. (encoding))
  100. (unless (string-match "\\`text/" type)
  101. (base64-encode-region (point-min) (point-max))
  102. (setq encoding "base64"))
  103. (goto-char (point-min))
  104. (nneething-make-head file (current-buffer)
  105. nil type charset encoding))
  106. (insert "\n"))
  107. t))))
  108. (deffoo nneething-request-group (group &optional server dont-check info)
  109. (nneething-possibly-change-directory group server)
  110. (unless dont-check
  111. (nneething-create-mapping)
  112. (if (> (car nneething-active) (cdr nneething-active))
  113. (nnheader-insert "211 0 1 0 %s\n" group)
  114. (nnheader-insert
  115. "211 %d %d %d %s\n"
  116. (- (1+ (cdr nneething-active)) (car nneething-active))
  117. (car nneething-active) (cdr nneething-active)
  118. group)))
  119. t)
  120. (deffoo nneething-request-list (&optional server dir)
  121. (nnheader-report 'nneething "LIST is not implemented."))
  122. (deffoo nneething-request-newgroups (date &optional server)
  123. (nnheader-report 'nneething "NEWSGROUPS is not implemented."))
  124. (deffoo nneething-request-type (group &optional article)
  125. 'unknown)
  126. (deffoo nneething-close-group (group &optional server)
  127. (setq nneething-current-directory nil)
  128. t)
  129. (deffoo nneething-open-server (server &optional defs)
  130. (nnheader-init-server-buffer)
  131. (if (nneething-server-opened server)
  132. t
  133. (unless (assq 'nneething-address defs)
  134. (setq defs (append defs (list (list 'nneething-address server)))))
  135. (nnoo-change-server 'nneething server defs)))
  136. ;;; Internal functions.
  137. (defun nneething-possibly-change-directory (group &optional server)
  138. (when (and server
  139. (not (nneething-server-opened server)))
  140. (nneething-open-server server))
  141. (when (and group
  142. (not (equal nneething-group group)))
  143. (setq nneething-group group)
  144. (setq nneething-map nil)
  145. (setq nneething-active (cons 1 0))
  146. (nneething-create-mapping)))
  147. (defun nneething-map-file ()
  148. ;; We make sure that the .nneething directory exists.
  149. (gnus-make-directory nneething-map-file-directory)
  150. ;; We store it in a special directory under the user's home dir.
  151. (concat (file-name-as-directory nneething-map-file-directory)
  152. nneething-group nneething-map-file))
  153. (defun nneething-create-mapping ()
  154. ;; Read nneething-active and nneething-map.
  155. (when (file-exists-p nneething-address)
  156. (let ((map-file (nneething-map-file))
  157. (files (directory-files nneething-address))
  158. touched map-files)
  159. (when (file-exists-p map-file)
  160. (ignore-errors
  161. (load map-file nil t t)))
  162. (unless nneething-active
  163. (setq nneething-active (cons 1 0)))
  164. ;; Old nneething had a different map format.
  165. (when (and (cdar nneething-map)
  166. (atom (cdar nneething-map)))
  167. (setq nneething-map
  168. (mapcar (lambda (n)
  169. (list (cdr n) (car n)
  170. (nth 5 (file-attributes
  171. (nneething-file-name (car n))))))
  172. nneething-map)))
  173. ;; Remove files matching the exclusion regexp.
  174. (when nneething-exclude-files
  175. (let ((f files)
  176. prev)
  177. (while f
  178. (if (string-match nneething-exclude-files (car f))
  179. (if prev (setcdr prev (cdr f))
  180. (setq files (cdr files)))
  181. (setq prev f))
  182. (setq f (cdr f)))))
  183. ;; Remove files not matching the inclusion regexp.
  184. (when nneething-include-files
  185. (let ((f files)
  186. prev)
  187. (while f
  188. (if (not (string-match nneething-include-files (car f)))
  189. (if prev (setcdr prev (cdr f))
  190. (setq files (cdr files)))
  191. (setq prev f))
  192. (setq f (cdr f)))))
  193. ;; Remove deleted files from the map.
  194. (let ((map nneething-map)
  195. prev)
  196. (while map
  197. (if (and (member (cadr (car map)) files)
  198. ;; We also remove files that have changed mod times.
  199. (equal (nth 5 (file-attributes
  200. (nneething-file-name (cadr (car map)))))
  201. (cadr (cdar map))))
  202. (progn
  203. (push (cadr (car map)) map-files)
  204. (setq prev map))
  205. (setq touched t)
  206. (if prev
  207. (setcdr prev (cdr map))
  208. (setq nneething-map (cdr nneething-map))))
  209. (setq map (cdr map))))
  210. ;; Find all new files and enter them into the map.
  211. (while files
  212. (unless (member (car files) map-files)
  213. ;; This file is not in the map, so we enter it.
  214. (setq touched t)
  215. (setcdr nneething-active (1+ (cdr nneething-active)))
  216. (push (list (cdr nneething-active) (car files)
  217. (nth 5 (file-attributes
  218. (nneething-file-name (car files)))))
  219. nneething-map))
  220. (setq files (cdr files)))
  221. (when (and touched
  222. (not nneething-read-only))
  223. (with-temp-file map-file
  224. (insert "(setq nneething-map '")
  225. (gnus-prin1 nneething-map)
  226. (insert ")\n(setq nneething-active '")
  227. (gnus-prin1 nneething-active)
  228. (insert ")\n"))))))
  229. (defun nneething-insert-head (file)
  230. "Insert the head of FILE."
  231. (when (nneething-get-head file)
  232. (insert-buffer-substring nneething-work-buffer)
  233. (goto-char (point-max))))
  234. (defun nneething-encode-file-name (file &optional coding-system)
  235. "Encode the name of the FILE in CODING-SYSTEM."
  236. (let ((pos 0) buf)
  237. (setq file (mm-encode-coding-string
  238. file (or coding-system nnmail-pathname-coding-system)))
  239. (while (string-match "[^-0-9a-zA-Z_:/.]" file pos)
  240. (setq buf (cons (format "%%%02x" (aref file (match-beginning 0)))
  241. (cons (substring file pos (match-beginning 0)) buf))
  242. pos (match-end 0)))
  243. (apply (function concat)
  244. (nreverse (cons (substring file pos) buf)))))
  245. (defun nneething-decode-file-name (file &optional coding-system)
  246. "Decode the name of the FILE is encoded in CODING-SYSTEM."
  247. (let ((pos 0) buf)
  248. (while (string-match "%\\([0-9a-fA-F][0-9a-fA-F]\\)" file pos)
  249. (setq buf (cons (string (string-to-number (match-string 1 file) 16))
  250. (cons (substring file pos (match-beginning 0)) buf))
  251. pos (match-end 0)))
  252. (mm-decode-coding-string
  253. (apply (function concat)
  254. (nreverse (cons (substring file pos) buf)))
  255. (or coding-system nnmail-pathname-coding-system))))
  256. (defun nneething-get-file-name (id)
  257. "Extract the file name from the message ID string."
  258. (when (string-match "\\`<nneething-\\([^@]+\\)@.*>\\'" id)
  259. (nneething-decode-file-name (match-string 1 id))))
  260. (defun nneething-make-head (file &optional buffer extra-msg
  261. mime-type mime-charset mime-encoding)
  262. "Create a head by looking at the file attributes of FILE."
  263. (let ((atts (file-attributes file)))
  264. (insert
  265. "Subject: " (file-name-nondirectory file) (or extra-msg "") "\n"
  266. "Message-ID: <nneething-" (nneething-encode-file-name file)
  267. "@" (system-name) ">\n"
  268. (if (equal '(0 0) (nth 5 atts)) ""
  269. (concat "Date: " (current-time-string (nth 5 atts)) "\n"))
  270. (or (when buffer
  271. (with-current-buffer buffer
  272. (when (re-search-forward "<[a-zA-Z0-9_]@[-a-zA-Z0-9_]>" 1000 t)
  273. (concat "From: " (match-string 0) "\n"))))
  274. (nneething-from-line (nth 2 atts) file))
  275. (if (> (string-to-number (int-to-string (nth 7 atts))) 0)
  276. (concat "Chars: " (int-to-string (nth 7 atts)) "\n")
  277. "")
  278. (if buffer
  279. (with-current-buffer buffer
  280. (concat "Lines: " (int-to-string
  281. (count-lines (point-min) (point-max)))
  282. "\n"))
  283. "")
  284. (if mime-type
  285. (concat "Content-Type: " mime-type
  286. (if mime-charset
  287. (concat "; charset="
  288. (if (stringp mime-charset)
  289. mime-charset
  290. (symbol-name mime-charset)))
  291. "")
  292. (if mime-encoding
  293. (concat "\nContent-Transfer-Encoding: " mime-encoding)
  294. "")
  295. "\nMIME-Version: 1.0\n")
  296. ""))))
  297. (defun nneething-from-line (uid &optional file)
  298. "Return a From header based of UID."
  299. (let* ((login (condition-case nil
  300. (user-login-name uid)
  301. (error
  302. (cond ((= uid (user-uid)) (user-login-name))
  303. ((zerop uid) "root")
  304. (t (int-to-string uid))))))
  305. (name (condition-case nil
  306. (user-full-name uid)
  307. (error
  308. (cond ((= uid (user-uid)) (user-full-name))
  309. ((zerop uid) "Ms. Root")))))
  310. (host (if (string-match "\\`/[^/@]*@\\([^:/]+\\):" file)
  311. (prog1
  312. (substring file
  313. (match-beginning 1)
  314. (match-end 1))
  315. (when (string-match
  316. "/\\(users\\|home\\)/\\([^/]+\\)/" file)
  317. (setq login (substring file
  318. (match-beginning 2)
  319. (match-end 2))
  320. name nil)))
  321. (system-name))))
  322. (concat "From: " login "@" host
  323. (if name (concat " (" name ")") "") "\n")))
  324. (defun nneething-get-head (file)
  325. "Either find the head in FILE or make a head for FILE."
  326. (with-current-buffer (get-buffer-create nneething-work-buffer)
  327. (setq case-fold-search nil)
  328. (buffer-disable-undo)
  329. (erase-buffer)
  330. (cond
  331. ((not (file-exists-p file))
  332. ;; The file do not exist.
  333. nil)
  334. ((or (file-directory-p file)
  335. (file-symlink-p file))
  336. ;; It's a dir, so we fudge a head.
  337. (nneething-make-head file) t)
  338. (t
  339. ;; We examine the file.
  340. (condition-case ()
  341. (progn
  342. (nnheader-insert-head file)
  343. (if (nnheader-article-p)
  344. (delete-region
  345. (progn
  346. (goto-char (point-min))
  347. (or (and (search-forward "\n\n" nil t)
  348. (1- (point)))
  349. (point-max)))
  350. (point-max))
  351. (goto-char (point-min))
  352. (nneething-make-head file (current-buffer))
  353. (delete-region (point) (point-max))))
  354. (file-error
  355. (nneething-make-head file (current-buffer) " (unreadable)")))
  356. t))))
  357. (defun nneething-file-name (article)
  358. "Return the file name of ARTICLE."
  359. (let ((dir (file-name-as-directory nneething-address))
  360. fname)
  361. (if (numberp article)
  362. (if (setq fname (cadr (assq article nneething-map)))
  363. (expand-file-name fname dir)
  364. (make-temp-name (expand-file-name "nneething" dir)))
  365. (expand-file-name article dir))))
  366. (provide 'nneething)
  367. ;;; nneething.el ends here