gnus-agent.el 157 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208
  1. ;;; gnus-agent.el --- unplugged support for Gnus
  2. ;; Copyright (C) 1997-2015 Free Software Foundation, Inc.
  3. ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
  4. ;; This file is part of GNU Emacs.
  5. ;; GNU Emacs is free software: you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; GNU Emacs is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;;; Code:
  17. (require 'gnus)
  18. (require 'gnus-cache)
  19. (require 'nnmail)
  20. (require 'nnvirtual)
  21. (require 'gnus-sum)
  22. (require 'gnus-score)
  23. (require 'gnus-srvr)
  24. (require 'gnus-util)
  25. (eval-when-compile
  26. (if (featurep 'xemacs)
  27. (require 'itimer)
  28. (require 'timer))
  29. (require 'cl))
  30. (autoload 'gnus-server-update-server "gnus-srvr")
  31. (autoload 'gnus-agent-customize-category "gnus-cus")
  32. (defcustom gnus-agent-directory (nnheader-concat gnus-directory "agent/")
  33. "Where the Gnus agent will store its files."
  34. :group 'gnus-agent
  35. :type 'directory)
  36. (defcustom gnus-agent-plugged-hook nil
  37. "Hook run when plugging into the network."
  38. :group 'gnus-agent
  39. :type 'hook)
  40. (defcustom gnus-agent-unplugged-hook nil
  41. "Hook run when unplugging from the network."
  42. :group 'gnus-agent
  43. :type 'hook)
  44. (defcustom gnus-agent-fetched-hook nil
  45. "Hook run when finished fetching articles."
  46. :version "22.1"
  47. :group 'gnus-agent
  48. :type 'hook)
  49. (defcustom gnus-agent-handle-level gnus-level-subscribed
  50. "Groups on levels higher than this variable will be ignored by the Agent."
  51. :group 'gnus-agent
  52. :type 'integer)
  53. (defcustom gnus-agent-expire-days 7
  54. "Read articles older than this will be expired.
  55. If you wish to disable Agent expiring, see `gnus-agent-enable-expiration'."
  56. :group 'gnus-agent
  57. :type '(number :tag "days"))
  58. (defcustom gnus-agent-expire-all nil
  59. "If non-nil, also expire unread, ticked and dormant articles.
  60. If nil, only read articles will be expired."
  61. :group 'gnus-agent
  62. :type 'boolean)
  63. (defcustom gnus-agent-group-mode-hook nil
  64. "Hook run in Agent group minor modes."
  65. :group 'gnus-agent
  66. :type 'hook)
  67. ;; Extracted from gnus-xmas-redefine in order to preserve user settings
  68. (when (featurep 'xemacs)
  69. (add-hook 'gnus-agent-group-mode-hook 'gnus-xmas-agent-group-menu-add))
  70. (defcustom gnus-agent-summary-mode-hook nil
  71. "Hook run in Agent summary minor modes."
  72. :group 'gnus-agent
  73. :type 'hook)
  74. ;; Extracted from gnus-xmas-redefine in order to preserve user settings
  75. (when (featurep 'xemacs)
  76. (add-hook 'gnus-agent-summary-mode-hook 'gnus-xmas-agent-summary-menu-add))
  77. (defcustom gnus-agent-server-mode-hook nil
  78. "Hook run in Agent summary minor modes."
  79. :group 'gnus-agent
  80. :type 'hook)
  81. ;; Extracted from gnus-xmas-redefine in order to preserve user settings
  82. (when (featurep 'xemacs)
  83. (add-hook 'gnus-agent-server-mode-hook 'gnus-xmas-agent-server-menu-add))
  84. (defcustom gnus-agent-confirmation-function 'y-or-n-p
  85. "Function to confirm when error happens."
  86. :version "21.1"
  87. :group 'gnus-agent
  88. :type 'function)
  89. (defcustom gnus-agent-synchronize-flags nil
  90. "Indicate if flags are synchronized when you plug in.
  91. If this is `ask' the hook will query the user."
  92. ;; If the default switches to something else than nil, then the function
  93. ;; should be fixed not be exceedingly slow. See 2005-09-20 ChangeLog entry.
  94. :version "21.1"
  95. :type '(choice (const :tag "Always" t)
  96. (const :tag "Never" nil)
  97. (const :tag "Ask" ask))
  98. :group 'gnus-agent)
  99. (defcustom gnus-agent-go-online 'ask
  100. "Indicate if offline servers go online when you plug in.
  101. If this is `ask' the hook will query the user."
  102. :version "21.3"
  103. :type '(choice (const :tag "Always" t)
  104. (const :tag "Never" nil)
  105. (const :tag "Ask" ask))
  106. :group 'gnus-agent)
  107. (defcustom gnus-agent-mark-unread-after-downloaded t
  108. "Indicate whether to mark articles unread after downloaded."
  109. :version "21.1"
  110. :type 'boolean
  111. :group 'gnus-agent)
  112. (defcustom gnus-agent-download-marks '(download)
  113. "Marks for downloading."
  114. :version "21.1"
  115. :type '(repeat (symbol :tag "Mark"))
  116. :group 'gnus-agent)
  117. (defcustom gnus-agent-consider-all-articles nil
  118. "When non-nil, the agent will let the agent predicate decide
  119. whether articles need to be downloaded or not, for all articles. When
  120. nil, the default, the agent will only let the predicate decide
  121. whether unread articles are downloaded or not. If you enable this,
  122. groups with large active ranges may open slower and you may also want
  123. to look into the agent expiry settings to block the expiration of
  124. read articles as they would just be downloaded again."
  125. :version "22.1"
  126. :type 'boolean
  127. :group 'gnus-agent)
  128. (defcustom gnus-agent-max-fetch-size 10000000 ;; 10 Mb
  129. "Chunk size for `gnus-agent-fetch-session'.
  130. The function will split its article fetches into chunks smaller than
  131. this limit."
  132. :version "22.1"
  133. :group 'gnus-agent
  134. :type 'integer)
  135. (defcustom gnus-agent-enable-expiration 'ENABLE
  136. "The default expiration state for each group.
  137. When set to ENABLE, the default, `gnus-agent-expire' will expire old
  138. contents from a group's local storage. This value may be overridden
  139. to disable expiration in specific categories, topics, and groups. Of
  140. course, you could change gnus-agent-enable-expiration to DISABLE then
  141. enable expiration per categories, topics, and groups."
  142. :version "22.1"
  143. :group 'gnus-agent
  144. :type '(radio (const :format "Enable " ENABLE)
  145. (const :format "Disable " DISABLE)))
  146. (defcustom gnus-agent-expire-unagentized-dirs t
  147. "*Whether expiration should expire in unagentized directories.
  148. Have gnus-agent-expire scan the directories under
  149. \(gnus-agent-directory) for groups that are no longer agentized.
  150. When found, offer to remove them."
  151. :version "22.1"
  152. :type 'boolean
  153. :group 'gnus-agent)
  154. (defcustom gnus-agent-auto-agentize-methods nil
  155. "Initially, all servers from these methods are agentized.
  156. The user may remove or add servers using the Server buffer.
  157. See Info nodes `(gnus)Server Buffer', `(gnus)Agent Variables'."
  158. :version "22.1"
  159. :type '(repeat symbol)
  160. :group 'gnus-agent)
  161. (defcustom gnus-agent-queue-mail t
  162. "Whether and when outgoing mail should be queued by the agent.
  163. When `always', always queue outgoing mail. When nil, never
  164. queue. Otherwise, queue if and only if unplugged."
  165. :version "22.1"
  166. :group 'gnus-agent
  167. :type '(radio (const :format "Always" always)
  168. (const :format "Never" nil)
  169. (const :format "When unplugged" t)))
  170. (defcustom gnus-agent-prompt-send-queue nil
  171. "If non-nil, `gnus-group-send-queue' will prompt if called when unplugged."
  172. :version "22.1"
  173. :group 'gnus-agent
  174. :type 'boolean)
  175. (defcustom gnus-agent-article-alist-save-format 1
  176. "Indicates whether to use compression(2), versus no
  177. compression(1), when writing agentview files. The compressed
  178. files do save space but load times are 6-7 times higher. A group
  179. must be opened then closed for the agentview to be updated using
  180. the new format."
  181. ;; Wouldn't symbols instead numbers be nicer? --rsteib
  182. :version "22.1"
  183. :group 'gnus-agent
  184. :type '(radio (const :format "Compressed" 2)
  185. (const :format "Uncompressed" 1)))
  186. ;;; Internal variables
  187. (defvar gnus-agent-history-buffers nil)
  188. (defvar gnus-agent-buffer-alist nil)
  189. (defvar gnus-agent-article-alist nil
  190. "An assoc list identifying the articles whose headers have been fetched.
  191. If successfully fetched, these headers will be stored in the group's overview
  192. file. The key of each assoc pair is the article ID, the value of each assoc
  193. pair is a flag indicating whether the identified article has been downloaded
  194. \(gnus-agent-fetch-articles sets the value to the day of the download).
  195. NOTES:
  196. 1) The last element of this list can not be expired as some
  197. routines (for example, get-agent-fetch-headers) use the last
  198. value to track which articles have had their headers retrieved.
  199. 2) The function `gnus-agent-regenerate' may destructively modify the value.")
  200. (defvar gnus-agent-group-alist nil)
  201. (defvar gnus-category-alist nil)
  202. (defvar gnus-agent-current-history nil)
  203. (defvar gnus-agent-overview-buffer nil)
  204. (defvar gnus-category-predicate-cache nil)
  205. (defvar gnus-category-group-cache nil)
  206. (defvar gnus-agent-spam-hashtb nil)
  207. (defvar gnus-agent-file-name nil)
  208. (defvar gnus-agent-file-coding-system 'raw-text)
  209. (defvar gnus-agent-file-loading-cache nil)
  210. (defvar gnus-agent-total-fetched-hashtb nil)
  211. (defvar gnus-agent-inhibit-update-total-fetched-for nil)
  212. (defvar gnus-agent-need-update-total-fetched-for nil)
  213. ;; Dynamic variables
  214. (defvar gnus-headers)
  215. (defvar gnus-score)
  216. ;; Added to support XEmacs
  217. (eval-and-compile
  218. (unless (fboundp 'directory-files-and-attributes)
  219. (defun directory-files-and-attributes (directory
  220. &optional full match nosort)
  221. (let (result)
  222. (dolist (file (directory-files directory full match nosort))
  223. (push (cons file (file-attributes file)) result))
  224. (nreverse result)))))
  225. ;;;
  226. ;;; Setup
  227. ;;;
  228. (defun gnus-open-agent ()
  229. (setq gnus-agent t)
  230. (gnus-agent-read-servers)
  231. (gnus-category-read)
  232. (gnus-agent-create-buffer)
  233. (add-hook 'gnus-group-mode-hook 'gnus-agent-mode)
  234. (add-hook 'gnus-summary-mode-hook 'gnus-agent-mode)
  235. (add-hook 'gnus-server-mode-hook 'gnus-agent-mode))
  236. (defun gnus-agent-create-buffer ()
  237. (if (gnus-buffer-live-p gnus-agent-overview-buffer)
  238. t
  239. (setq gnus-agent-overview-buffer
  240. (gnus-get-buffer-create " *Gnus agent overview*"))
  241. (with-current-buffer gnus-agent-overview-buffer
  242. (mm-enable-multibyte))
  243. nil))
  244. (gnus-add-shutdown 'gnus-close-agent 'gnus)
  245. (defun gnus-close-agent ()
  246. (setq gnus-category-predicate-cache nil
  247. gnus-category-group-cache nil
  248. gnus-agent-spam-hashtb nil)
  249. (gnus-kill-buffer gnus-agent-overview-buffer))
  250. ;;;
  251. ;;; Utility functions
  252. ;;;
  253. (defmacro gnus-agent-with-refreshed-group (group &rest body)
  254. "Performs the body then updates the group's line in the group
  255. buffer. Automatically blocks multiple updates due to recursion."
  256. `(prog1 (let ((gnus-agent-inhibit-update-total-fetched-for t)) ,@body)
  257. (when (and gnus-agent-need-update-total-fetched-for
  258. (not gnus-agent-inhibit-update-total-fetched-for))
  259. (with-current-buffer gnus-group-buffer
  260. (setq gnus-agent-need-update-total-fetched-for nil)
  261. (gnus-group-update-group ,group t)))))
  262. (defun gnus-agent-read-file (file)
  263. "Load FILE and do a `read' there."
  264. (with-temp-buffer
  265. (ignore-errors
  266. (nnheader-insert-file-contents file)
  267. (goto-char (point-min))
  268. (read (current-buffer)))))
  269. (defsubst gnus-agent-method ()
  270. (concat (symbol-name (car gnus-command-method)) "/"
  271. (if (equal (cadr gnus-command-method) "")
  272. "unnamed"
  273. (cadr gnus-command-method))))
  274. (defsubst gnus-agent-directory ()
  275. "The name of the Gnus agent directory."
  276. (nnheader-concat gnus-agent-directory
  277. (nnheader-translate-file-chars (gnus-agent-method)) "/"))
  278. (defun gnus-agent-lib-file (file)
  279. "The full name of the Gnus agent library FILE."
  280. (expand-file-name file
  281. (file-name-as-directory
  282. (expand-file-name "agent.lib" (gnus-agent-directory)))))
  283. (defun gnus-agent-cat-set-property (category property value)
  284. (if value
  285. (setcdr (or (assq property category)
  286. (let ((cell (cons property nil)))
  287. (setcdr category (cons cell (cdr category)))
  288. cell)) value)
  289. (let ((category category))
  290. (while (cond ((eq property (caadr category))
  291. (setcdr category (cddr category))
  292. nil)
  293. (t
  294. (setq category (cdr category)))))))
  295. category)
  296. (eval-when-compile
  297. (defmacro gnus-agent-cat-defaccessor (name prop-name)
  298. "Define accessor and setter methods for manipulating a list of the form
  299. \(NAME (PROPERTY1 VALUE1) ... (PROPERTY_N VALUE_N)).
  300. Given the call (gnus-agent-cat-defaccessor func PROPERTY1), the list may be
  301. manipulated as follows:
  302. (func LIST): Returns VALUE1
  303. (setf (func LIST) NEW_VALUE1): Replaces VALUE1 with NEW_VALUE1."
  304. `(progn (defmacro ,name (category)
  305. (list 'cdr (list 'assq '',prop-name category)))
  306. (defsetf ,name (category) (value)
  307. (list 'gnus-agent-cat-set-property
  308. category '',prop-name value))))
  309. )
  310. (defmacro gnus-agent-cat-name (category)
  311. `(car ,category))
  312. (gnus-agent-cat-defaccessor
  313. gnus-agent-cat-days-until-old agent-days-until-old)
  314. (gnus-agent-cat-defaccessor
  315. gnus-agent-cat-enable-expiration agent-enable-expiration)
  316. (gnus-agent-cat-defaccessor
  317. gnus-agent-cat-groups agent-groups)
  318. (gnus-agent-cat-defaccessor
  319. gnus-agent-cat-high-score agent-high-score)
  320. (gnus-agent-cat-defaccessor
  321. gnus-agent-cat-length-when-long agent-long-article)
  322. (gnus-agent-cat-defaccessor
  323. gnus-agent-cat-length-when-short agent-short-article)
  324. (gnus-agent-cat-defaccessor
  325. gnus-agent-cat-low-score agent-low-score)
  326. (gnus-agent-cat-defaccessor
  327. gnus-agent-cat-predicate agent-predicate)
  328. (gnus-agent-cat-defaccessor
  329. gnus-agent-cat-score-file agent-score)
  330. (gnus-agent-cat-defaccessor
  331. gnus-agent-cat-enable-undownloaded-faces agent-enable-undownloaded-faces)
  332. ;; This form may expand to code that uses CL functions at run-time,
  333. ;; but that's OK since those functions will only ever be called from
  334. ;; something like `setf', so only when CL is loaded anyway.
  335. (defsetf gnus-agent-cat-groups gnus-agent-set-cat-groups)
  336. (defun gnus-agent-set-cat-groups (category groups)
  337. (unless (eq groups 'ignore)
  338. (let ((new-g groups)
  339. (old-g (gnus-agent-cat-groups category)))
  340. (cond ((eq new-g old-g)
  341. ;; gnus-agent-add-group is fiddling with the group
  342. ;; list. Still, Im done.
  343. nil
  344. )
  345. ((eq new-g (cdr old-g))
  346. ;; gnus-agent-add-group is fiddling with the group list
  347. (setcdr (or (assq 'agent-groups category)
  348. (let ((cell (cons 'agent-groups nil)))
  349. (setcdr category (cons cell (cdr category)))
  350. cell)) new-g))
  351. (t
  352. (let ((groups groups))
  353. (while groups
  354. (let* ((group (pop groups))
  355. (old-category (gnus-group-category group)))
  356. (if (eq category old-category)
  357. nil
  358. (setf (gnus-agent-cat-groups old-category)
  359. (delete group (gnus-agent-cat-groups
  360. old-category))))))
  361. ;; Purge cache as preceding loop invalidated it.
  362. (setq gnus-category-group-cache nil))
  363. (setcdr (or (assq 'agent-groups category)
  364. (let ((cell (cons 'agent-groups nil)))
  365. (setcdr category (cons cell (cdr category)))
  366. cell)) groups))))))
  367. (defsubst gnus-agent-cat-make (name &optional default-agent-predicate)
  368. (list name `(agent-predicate . ,(or default-agent-predicate 'false))))
  369. (defun gnus-agent-read-group ()
  370. "Read a group name in the minibuffer, with completion."
  371. (let ((def (or (gnus-group-group-name) gnus-newsgroup-name)))
  372. (when def
  373. (setq def (gnus-group-decoded-name def)))
  374. (gnus-group-completing-read nil nil t nil nil def)))
  375. ;;; Fetching setup functions.
  376. (defun gnus-agent-start-fetch ()
  377. "Initialize data structures for efficient fetching."
  378. (gnus-agent-create-buffer))
  379. (defun gnus-agent-stop-fetch ()
  380. "Save all data structures and clean up."
  381. (setq gnus-agent-spam-hashtb nil)
  382. (with-current-buffer nntp-server-buffer
  383. (widen)))
  384. (defmacro gnus-agent-with-fetch (&rest forms)
  385. "Do FORMS safely."
  386. `(unwind-protect
  387. (let ((gnus-agent-fetching t))
  388. (gnus-agent-start-fetch)
  389. ,@forms)
  390. (gnus-agent-stop-fetch)))
  391. (put 'gnus-agent-with-fetch 'lisp-indent-function 0)
  392. (put 'gnus-agent-with-fetch 'edebug-form-spec '(body))
  393. (defmacro gnus-agent-append-to-list (tail value)
  394. `(setq ,tail (setcdr ,tail (cons ,value nil))))
  395. (defmacro gnus-agent-message (level &rest args)
  396. `(if (<= ,level gnus-verbose)
  397. (message ,@args)))
  398. ;;;
  399. ;;; Mode infestation
  400. ;;;
  401. (defvar gnus-agent-mode-hook nil
  402. "Hook run when installing agent mode.")
  403. (defvar gnus-agent-mode nil)
  404. (defvar gnus-agent-mode-status '(gnus-agent-mode " Plugged"))
  405. (defun gnus-agent-mode ()
  406. "Minor mode for providing a agent support in Gnus buffers."
  407. (let* ((buffer (progn (string-match "^gnus-\\(.*\\)-mode$"
  408. (symbol-name major-mode))
  409. (match-string 1 (symbol-name major-mode))))
  410. (mode (intern (format "gnus-agent-%s-mode" buffer))))
  411. (set (make-local-variable 'gnus-agent-mode) t)
  412. (set mode nil)
  413. (set (make-local-variable mode) t)
  414. ;; Set up the menu.
  415. (when (gnus-visual-p 'agent-menu 'menu)
  416. (funcall (intern (format "gnus-agent-%s-make-menu-bar" buffer))))
  417. (unless (assq mode minor-mode-alist)
  418. (push (cons mode (cdr gnus-agent-mode-status)) minor-mode-alist))
  419. (unless (assq mode minor-mode-map-alist)
  420. (push (cons mode (symbol-value (intern (format "gnus-agent-%s-mode-map"
  421. buffer))))
  422. minor-mode-map-alist))
  423. (when (derived-mode-p 'gnus-group-mode)
  424. (let ((init-plugged gnus-plugged)
  425. (gnus-agent-go-online nil))
  426. ;; g-a-t-p does nothing when gnus-plugged isn't changed.
  427. ;; Therefore, make certain that the current value does not
  428. ;; match the desired initial value.
  429. (setq gnus-plugged :unknown)
  430. (gnus-agent-toggle-plugged init-plugged)))
  431. (gnus-run-hooks 'gnus-agent-mode-hook
  432. (intern (format "gnus-agent-%s-mode-hook" buffer)))))
  433. (defvar gnus-agent-group-mode-map (make-sparse-keymap))
  434. (gnus-define-keys gnus-agent-group-mode-map
  435. "Ju" gnus-agent-fetch-groups
  436. "Jc" gnus-enter-category-buffer
  437. "Jj" gnus-agent-toggle-plugged
  438. "Js" gnus-agent-fetch-session
  439. "JY" gnus-agent-synchronize-flags
  440. "JS" gnus-group-send-queue
  441. "Ja" gnus-agent-add-group
  442. "Jr" gnus-agent-remove-group
  443. "Jo" gnus-agent-toggle-group-plugged)
  444. (defun gnus-agent-group-make-menu-bar ()
  445. (unless (boundp 'gnus-agent-group-menu)
  446. (easy-menu-define
  447. gnus-agent-group-menu gnus-agent-group-mode-map ""
  448. '("Agent"
  449. ["Toggle plugged" gnus-agent-toggle-plugged t]
  450. ["Toggle group plugged" gnus-agent-toggle-group-plugged t]
  451. ["List categories" gnus-enter-category-buffer t]
  452. ["Add (current) group to category" gnus-agent-add-group t]
  453. ["Remove (current) group from category" gnus-agent-remove-group t]
  454. ["Send queue" gnus-group-send-queue gnus-plugged]
  455. ("Fetch"
  456. ["All" gnus-agent-fetch-session gnus-plugged]
  457. ["Group" gnus-agent-fetch-group gnus-plugged])
  458. ["Synchronize flags" gnus-agent-synchronize-flags t]
  459. ))))
  460. (defvar gnus-agent-summary-mode-map (make-sparse-keymap))
  461. (gnus-define-keys gnus-agent-summary-mode-map
  462. "Jj" gnus-agent-toggle-plugged
  463. "Ju" gnus-agent-summary-fetch-group
  464. "JS" gnus-agent-fetch-group
  465. "Js" gnus-agent-summary-fetch-series
  466. "J#" gnus-agent-mark-article
  467. "J\M-#" gnus-agent-unmark-article
  468. "@" gnus-agent-toggle-mark
  469. "Jc" gnus-agent-catchup)
  470. (defun gnus-agent-summary-make-menu-bar ()
  471. (unless (boundp 'gnus-agent-summary-menu)
  472. (easy-menu-define
  473. gnus-agent-summary-menu gnus-agent-summary-mode-map ""
  474. '("Agent"
  475. ["Toggle plugged" gnus-agent-toggle-plugged t]
  476. ["Mark as downloadable" gnus-agent-mark-article t]
  477. ["Unmark as downloadable" gnus-agent-unmark-article t]
  478. ["Toggle mark" gnus-agent-toggle-mark t]
  479. ["Fetch downloadable" gnus-agent-summary-fetch-group t]
  480. ["Catchup undownloaded" gnus-agent-catchup t]))))
  481. (defvar gnus-agent-server-mode-map (make-sparse-keymap))
  482. (gnus-define-keys gnus-agent-server-mode-map
  483. "Jj" gnus-agent-toggle-plugged
  484. "Ja" gnus-agent-add-server
  485. "Jr" gnus-agent-remove-server)
  486. (defun gnus-agent-server-make-menu-bar ()
  487. (unless (boundp 'gnus-agent-server-menu)
  488. (easy-menu-define
  489. gnus-agent-server-menu gnus-agent-server-mode-map ""
  490. '("Agent"
  491. ["Toggle plugged" gnus-agent-toggle-plugged t]
  492. ["Add" gnus-agent-add-server t]
  493. ["Remove" gnus-agent-remove-server t]))))
  494. (defun gnus-agent-make-mode-line-string (string mouse-button mouse-func)
  495. (if (and (fboundp 'propertize)
  496. (fboundp 'make-mode-line-mouse-map))
  497. (propertize string 'local-map
  498. (make-mode-line-mouse-map mouse-button mouse-func)
  499. 'mouse-face
  500. (if (and (featurep 'xemacs)
  501. ;; XEmacs's `facep' only checks for a face
  502. ;; object, not for a face name, so it's useless
  503. ;; to check with `facep'.
  504. (find-face 'modeline))
  505. 'modeline
  506. 'mode-line-highlight))
  507. string))
  508. (defun gnus-agent-toggle-plugged (set-to)
  509. "Toggle whether Gnus is unplugged or not."
  510. (interactive (list (not gnus-plugged)))
  511. (cond ((eq set-to gnus-plugged)
  512. nil)
  513. (set-to
  514. (setq gnus-plugged set-to)
  515. (gnus-run-hooks 'gnus-agent-plugged-hook)
  516. (setcar (cdr gnus-agent-mode-status)
  517. (gnus-agent-make-mode-line-string " Plugged"
  518. 'mouse-2
  519. 'gnus-agent-toggle-plugged))
  520. (gnus-agent-go-online gnus-agent-go-online))
  521. (t
  522. (gnus-agent-close-connections)
  523. (setq gnus-plugged set-to)
  524. (gnus-run-hooks 'gnus-agent-unplugged-hook)
  525. (setcar (cdr gnus-agent-mode-status)
  526. (gnus-agent-make-mode-line-string " Unplugged"
  527. 'mouse-2
  528. 'gnus-agent-toggle-plugged))))
  529. (set-buffer-modified-p t))
  530. (defmacro gnus-agent-while-plugged (&rest body)
  531. `(let ((original-gnus-plugged gnus-plugged))
  532. (unwind-protect
  533. (progn (gnus-agent-toggle-plugged t)
  534. ,@body)
  535. (gnus-agent-toggle-plugged original-gnus-plugged))))
  536. (put 'gnus-agent-while-plugged 'lisp-indent-function 0)
  537. (put 'gnus-agent-while-plugged 'edebug-form-spec '(body))
  538. (defun gnus-agent-close-connections ()
  539. "Close all methods covered by the Gnus agent."
  540. (let ((methods (gnus-agent-covered-methods)))
  541. (while methods
  542. (gnus-close-server (pop methods)))))
  543. ;;;###autoload
  544. (defun gnus-unplugged ()
  545. "Start Gnus unplugged."
  546. (interactive)
  547. (setq gnus-plugged nil)
  548. (gnus))
  549. ;;;###autoload
  550. (defun gnus-plugged ()
  551. "Start Gnus plugged."
  552. (interactive)
  553. (setq gnus-plugged t)
  554. (gnus))
  555. ;;;###autoload
  556. (defun gnus-slave-unplugged (&optional arg)
  557. "Read news as a slave unplugged."
  558. (interactive "P")
  559. (setq gnus-plugged nil)
  560. (gnus arg nil 'slave))
  561. ;;;###autoload
  562. (defun gnus-agentize ()
  563. "Allow Gnus to be an offline newsreader.
  564. The gnus-agentize function is now called internally by gnus when
  565. gnus-agent is set. If you wish to avoid calling gnus-agentize,
  566. customize gnus-agent to nil.
  567. This will modify the `gnus-setup-news-hook', and
  568. `message-send-mail-real-function' variables, and install the Gnus agent
  569. minor mode in all Gnus buffers."
  570. (interactive)
  571. (gnus-open-agent)
  572. (setq message-send-mail-real-function 'gnus-agent-send-mail)
  573. ;; If the servers file doesn't exist, auto-agentize some servers and
  574. ;; save the servers file so this auto-agentizing isn't invoked
  575. ;; again.
  576. (when (and (not (file-exists-p (nnheader-concat
  577. gnus-agent-directory "lib/servers")))
  578. gnus-agent-auto-agentize-methods)
  579. (gnus-message 3 "First time agent user, agentizing remote groups...")
  580. (mapc
  581. (lambda (server-or-method)
  582. (let ((method (gnus-server-to-method server-or-method)))
  583. (when (memq (car method)
  584. gnus-agent-auto-agentize-methods)
  585. (push (gnus-method-to-server method)
  586. gnus-agent-covered-methods)
  587. (setq gnus-agent-method-p-cache nil))))
  588. (cons gnus-select-method gnus-secondary-select-methods))
  589. (gnus-agent-write-servers)))
  590. (defun gnus-agent-queue-setup (&optional group-name)
  591. "Make sure the queue group exists.
  592. Optional arg GROUP-NAME allows to specify another group."
  593. (unless (gnus-gethash (format "nndraft:%s" (or group-name "queue"))
  594. gnus-newsrc-hashtb)
  595. (gnus-request-create-group (or group-name "queue") '(nndraft ""))
  596. (let ((gnus-level-default-subscribed 1))
  597. (gnus-subscribe-group (format "nndraft:%s" (or group-name "queue"))
  598. nil '(nndraft "")))
  599. (gnus-group-set-parameter
  600. (format "nndraft:%s" (or group-name "queue"))
  601. 'gnus-dummy '((gnus-draft-mode)))))
  602. (defun gnus-agent-send-mail ()
  603. (if (or (not gnus-agent-queue-mail)
  604. (and gnus-plugged (not (eq gnus-agent-queue-mail 'always))))
  605. (message-multi-smtp-send-mail)
  606. (goto-char (point-min))
  607. (re-search-forward
  608. (concat "^" (regexp-quote mail-header-separator) "\n"))
  609. (replace-match "\n")
  610. (gnus-agent-insert-meta-information 'mail)
  611. (gnus-request-accept-article "nndraft:queue" nil t t)
  612. (gnus-group-refresh-group "nndraft:queue")))
  613. (defun gnus-agent-insert-meta-information (type &optional method)
  614. "Insert meta-information into the message that says how it's to be posted.
  615. TYPE can be either `mail' or `news'. If the latter, then METHOD can
  616. be a select method."
  617. (save-excursion
  618. (message-remove-header gnus-agent-meta-information-header)
  619. (goto-char (point-min))
  620. (insert gnus-agent-meta-information-header ": "
  621. (symbol-name type) " " (format "%S" method)
  622. "\n")
  623. (forward-char -1)
  624. (while (search-backward "\n" nil t)
  625. (replace-match "\\n" t t))))
  626. (defun gnus-agent-restore-gcc ()
  627. "Restore GCC field from saved header."
  628. (save-excursion
  629. (goto-char (point-min))
  630. (while (re-search-forward
  631. (concat "^" (regexp-quote gnus-agent-gcc-header) ":") nil t)
  632. (replace-match "Gcc:" 'fixedcase))))
  633. (defun gnus-agent-any-covered-gcc ()
  634. (save-restriction
  635. (message-narrow-to-headers)
  636. (let* ((gcc (mail-fetch-field "gcc" nil t))
  637. (methods (and gcc
  638. (mapcar 'gnus-inews-group-method
  639. (message-unquote-tokens
  640. (message-tokenize-header
  641. gcc " ,")))))
  642. covered)
  643. (while (and (not covered) methods)
  644. (setq covered (gnus-agent-method-p (car methods))
  645. methods (cdr methods)))
  646. covered)))
  647. ;;;###autoload
  648. (defun gnus-agent-possibly-save-gcc ()
  649. "Save GCC if Gnus is unplugged."
  650. (when (and (not gnus-plugged) (gnus-agent-any-covered-gcc))
  651. (save-excursion
  652. (goto-char (point-min))
  653. (let ((case-fold-search t))
  654. (while (re-search-forward "^gcc:" nil t)
  655. (replace-match (concat gnus-agent-gcc-header ":") 'fixedcase))))))
  656. (defun gnus-agent-possibly-do-gcc ()
  657. "Do GCC if Gnus is plugged."
  658. (when (or gnus-plugged (not (gnus-agent-any-covered-gcc)))
  659. (gnus-inews-do-gcc)))
  660. ;;;
  661. ;;; Group mode commands
  662. ;;;
  663. (defun gnus-agent-fetch-groups (n)
  664. "Put all new articles in the current groups into the Agent."
  665. (interactive "P")
  666. (unless gnus-plugged
  667. (error "Groups can't be fetched when Gnus is unplugged"))
  668. (gnus-group-iterate n 'gnus-agent-fetch-group))
  669. (defun gnus-agent-fetch-group (&optional group)
  670. "Put all new articles in GROUP into the Agent."
  671. (interactive (list (gnus-group-group-name)))
  672. (setq group (or group gnus-newsgroup-name))
  673. (unless group
  674. (error "No group on the current line"))
  675. (if (not (gnus-agent-group-covered-p group))
  676. (message "%s isn't covered by the agent" group)
  677. (gnus-agent-while-plugged
  678. (let ((gnus-command-method (gnus-find-method-for-group group)))
  679. (gnus-agent-with-fetch
  680. (gnus-agent-fetch-group-1 group gnus-command-method)
  681. (gnus-message 5 "Fetching %s...done" group))))))
  682. (defun gnus-agent-add-group (category arg)
  683. "Add the current group to an agent category."
  684. (interactive
  685. (list
  686. (intern
  687. (gnus-completing-read
  688. "Add to category"
  689. (mapcar (lambda (cat) (symbol-name (car cat)))
  690. gnus-category-alist)
  691. t))
  692. current-prefix-arg))
  693. (let ((cat (assq category gnus-category-alist))
  694. c groups)
  695. (gnus-group-iterate arg
  696. (lambda (group)
  697. (when (gnus-agent-cat-groups (setq c (gnus-group-category group)))
  698. (setf (gnus-agent-cat-groups c)
  699. (delete group (gnus-agent-cat-groups c))))
  700. (push group groups)))
  701. (setf (gnus-agent-cat-groups cat)
  702. (nconc (gnus-agent-cat-groups cat) groups))
  703. (gnus-category-write)))
  704. (defun gnus-agent-remove-group (arg)
  705. "Remove the current group from its agent category, if any."
  706. (interactive "P")
  707. (let (c)
  708. (gnus-group-iterate arg
  709. (lambda (group)
  710. (when (gnus-agent-cat-groups (setq c (gnus-group-category group)))
  711. (setf (gnus-agent-cat-groups c)
  712. (delete group (gnus-agent-cat-groups c))))))
  713. (gnus-category-write)))
  714. (defun gnus-agent-synchronize-flags ()
  715. "Synchronize unplugged flags with servers."
  716. (interactive)
  717. (save-excursion
  718. (dolist (gnus-command-method (gnus-agent-covered-methods))
  719. (when (file-exists-p (gnus-agent-lib-file "flags"))
  720. (gnus-agent-synchronize-flags-server gnus-command-method)))))
  721. (defun gnus-agent-possibly-synchronize-flags ()
  722. "Synchronize flags according to `gnus-agent-synchronize-flags'."
  723. (interactive)
  724. (save-excursion
  725. (dolist (gnus-command-method (gnus-agent-covered-methods))
  726. (when (eq (gnus-server-status gnus-command-method) 'ok)
  727. (gnus-agent-possibly-synchronize-flags-server gnus-command-method)))))
  728. (defun gnus-agent-synchronize-flags-server (method)
  729. "Synchronize flags set when unplugged for server."
  730. (let ((gnus-command-method method)
  731. (gnus-agent nil))
  732. (when (file-exists-p (gnus-agent-lib-file "flags"))
  733. (set-buffer (get-buffer-create " *Gnus Agent flag synchronize*"))
  734. (erase-buffer)
  735. (nnheader-insert-file-contents (gnus-agent-lib-file "flags"))
  736. (cond ((null gnus-plugged)
  737. (gnus-message
  738. 1 "You must be plugged to synchronize flags with server %s"
  739. (nth 1 gnus-command-method)))
  740. ((null (gnus-check-server gnus-command-method))
  741. (gnus-message
  742. 1 "Couldn't open server %s" (nth 1 gnus-command-method)))
  743. (t
  744. (condition-case err
  745. (while t
  746. (let ((bgn (point)))
  747. (eval (read (current-buffer)))
  748. (delete-region bgn (point))))
  749. (end-of-file
  750. (delete-file (gnus-agent-lib-file "flags")))
  751. (error
  752. (let ((file (gnus-agent-lib-file "flags")))
  753. (write-region (point-min) (point-max)
  754. (gnus-agent-lib-file "flags") nil 'silent)
  755. (error "Couldn't set flags from file %s due to %s"
  756. file (error-message-string err)))))))
  757. (kill-buffer nil))))
  758. (defun gnus-agent-possibly-synchronize-flags-server (method)
  759. "Synchronize flags for server according to `gnus-agent-synchronize-flags'."
  760. (when (and (file-exists-p (gnus-agent-lib-file "flags"))
  761. (or (and gnus-agent-synchronize-flags
  762. (not (eq gnus-agent-synchronize-flags 'ask)))
  763. (and (eq gnus-agent-synchronize-flags 'ask)
  764. (gnus-y-or-n-p
  765. (gnus-format-message
  766. "Synchronize flags on server `%s'? "
  767. (cadr method))))))
  768. (gnus-agent-synchronize-flags-server method)))
  769. ;;;###autoload
  770. (defun gnus-agent-rename-group (old-group new-group)
  771. "Rename fully-qualified OLD-GROUP as NEW-GROUP.
  772. Always updates the agent, even when disabled, as the old agent
  773. files would corrupt gnus when the agent was next enabled.
  774. Depends upon the caller to determine whether group renaming is
  775. supported."
  776. (let* ((old-command-method (gnus-find-method-for-group old-group))
  777. (old-path (directory-file-name
  778. (let ((gnus-command-method old-command-method))
  779. (gnus-agent-group-pathname old-group))))
  780. (new-command-method (gnus-find-method-for-group new-group))
  781. (new-path (directory-file-name
  782. (let ((gnus-command-method new-command-method))
  783. (gnus-agent-group-pathname new-group))))
  784. (file-name-coding-system nnmail-pathname-coding-system))
  785. (gnus-rename-file old-path new-path t)
  786. (let* ((old-real-group (gnus-group-real-name old-group))
  787. (new-real-group (gnus-group-real-name new-group))
  788. (old-active (gnus-agent-get-group-info old-command-method old-real-group)))
  789. (gnus-agent-save-group-info old-command-method old-real-group nil)
  790. (gnus-agent-save-group-info new-command-method new-real-group old-active)
  791. (let ((old-local (gnus-agent-get-local old-group
  792. old-real-group old-command-method)))
  793. (gnus-agent-set-local old-group
  794. nil nil
  795. old-real-group old-command-method)
  796. (gnus-agent-set-local new-group
  797. (car old-local) (cdr old-local)
  798. new-real-group new-command-method)))))
  799. ;;;###autoload
  800. (defun gnus-agent-delete-group (group)
  801. "Delete fully-qualified GROUP.
  802. Always updates the agent, even when disabled, as the old agent
  803. files would corrupt gnus when the agent was next enabled.
  804. Depends upon the caller to determine whether group deletion is
  805. supported."
  806. (let* ((command-method (gnus-find-method-for-group group))
  807. (path (directory-file-name
  808. (let ((gnus-command-method command-method))
  809. (gnus-agent-group-pathname group))))
  810. (file-name-coding-system nnmail-pathname-coding-system))
  811. (gnus-delete-directory path)
  812. (let* ((real-group (gnus-group-real-name group)))
  813. (gnus-agent-save-group-info command-method real-group nil)
  814. ;; FIXME: Does gnus-agent-get-local have any useful side-effect?
  815. (gnus-agent-get-local group real-group command-method)
  816. (gnus-agent-set-local group
  817. nil nil
  818. real-group command-method))))
  819. ;;;
  820. ;;; Server mode commands
  821. ;;;
  822. (defun gnus-agent-add-server ()
  823. "Enroll SERVER in the agent program."
  824. (interactive)
  825. (let* ((server (gnus-server-server-name))
  826. (named-server (gnus-server-named-server))
  827. (method (and server
  828. (gnus-server-get-method nil server))))
  829. (unless server
  830. (error "No server on the current line"))
  831. (when (gnus-agent-method-p method)
  832. (error "Server already in the agent program"))
  833. (push named-server gnus-agent-covered-methods)
  834. (setq gnus-agent-method-p-cache nil)
  835. (gnus-server-update-server server)
  836. (gnus-agent-write-servers)
  837. (gnus-message 1 "Entered %s into the Agent" server)))
  838. (defun gnus-agent-remove-server ()
  839. "Remove SERVER from the agent program."
  840. (interactive)
  841. (let* ((server (gnus-server-server-name))
  842. (named-server (gnus-server-named-server)))
  843. (unless server
  844. (error "No server on the current line"))
  845. (unless (member named-server gnus-agent-covered-methods)
  846. (error "Server not in the agent program"))
  847. (setq gnus-agent-covered-methods
  848. (delete named-server gnus-agent-covered-methods)
  849. gnus-agent-method-p-cache nil)
  850. (gnus-server-update-server server)
  851. (gnus-agent-write-servers)
  852. (gnus-message 1 "Removed %s from the agent" server)))
  853. (defun gnus-agent-read-servers ()
  854. "Read the alist of covered servers."
  855. (setq gnus-agent-covered-methods
  856. (gnus-agent-read-file
  857. (nnheader-concat gnus-agent-directory "lib/servers"))
  858. gnus-agent-method-p-cache nil)
  859. ;; I am called so early in start-up that I can not validate server
  860. ;; names. When that is the case, I skip the validation. That is
  861. ;; alright as the gnus startup code calls the validate methods
  862. ;; directly.
  863. (if gnus-server-alist
  864. (gnus-agent-read-servers-validate)))
  865. (defun gnus-agent-read-servers-validate ()
  866. (mapcar (lambda (server-or-method)
  867. (let* ((server (if (stringp server-or-method)
  868. server-or-method
  869. (gnus-method-to-server server-or-method)))
  870. (method (gnus-server-to-method server)))
  871. (if method
  872. (unless (member server gnus-agent-covered-methods)
  873. (push server gnus-agent-covered-methods)
  874. (setq gnus-agent-method-p-cache nil))
  875. (gnus-message 8 "Ignoring disappeared server `%s'" server))))
  876. (prog1 gnus-agent-covered-methods
  877. (setq gnus-agent-covered-methods nil))))
  878. (defun gnus-agent-read-servers-validate-native (native-method)
  879. (setq gnus-agent-covered-methods
  880. (mapcar (lambda (method)
  881. (if (or (not method)
  882. (equal method native-method))
  883. "native"
  884. method)) gnus-agent-covered-methods)))
  885. (defun gnus-agent-write-servers ()
  886. "Write the alist of covered servers."
  887. (gnus-make-directory (nnheader-concat gnus-agent-directory "lib"))
  888. (let ((coding-system-for-write nnheader-file-coding-system)
  889. (file-name-coding-system nnmail-pathname-coding-system))
  890. (with-temp-file (nnheader-concat gnus-agent-directory "lib/servers")
  891. (prin1 gnus-agent-covered-methods
  892. (current-buffer)))))
  893. ;;;
  894. ;;; Summary commands
  895. ;;;
  896. (defun gnus-agent-mark-article (n &optional unmark)
  897. "Mark the next N articles as downloadable.
  898. If N is negative, mark backward instead. If UNMARK is non-nil, remove
  899. the mark instead. The difference between N and the actual number of
  900. articles marked is returned."
  901. (interactive "p")
  902. (let ((backward (< n 0))
  903. (n (abs n)))
  904. (while (and
  905. (> n 0)
  906. (progn
  907. (gnus-summary-set-agent-mark
  908. (gnus-summary-article-number) unmark)
  909. (zerop (gnus-summary-next-subject (if backward -1 1) nil t))))
  910. (setq n (1- n)))
  911. (when (/= 0 n)
  912. (gnus-message 7 "No more articles"))
  913. (gnus-summary-recenter)
  914. (gnus-summary-position-point)
  915. n))
  916. (defun gnus-agent-unmark-article (n)
  917. "Remove the downloadable mark from the next N articles.
  918. If N is negative, unmark backward instead. The difference between N and
  919. the actual number of articles unmarked is returned."
  920. (interactive "p")
  921. (gnus-agent-mark-article n t))
  922. (defun gnus-agent-toggle-mark (n)
  923. "Toggle the downloadable mark from the next N articles.
  924. If N is negative, toggle backward instead. The difference between N and
  925. the actual number of articles toggled is returned."
  926. (interactive "p")
  927. (gnus-agent-mark-article n 'toggle))
  928. (defun gnus-summary-set-agent-mark (article &optional unmark)
  929. "Mark ARTICLE as downloadable. If UNMARK is nil, article is marked.
  930. When UNMARK is t, the article is unmarked. For any other value, the
  931. article's mark is toggled."
  932. (let ((unmark (cond ((eq nil unmark)
  933. nil)
  934. ((eq t unmark)
  935. t)
  936. (t
  937. (memq article gnus-newsgroup-downloadable)))))
  938. (when (gnus-summary-goto-subject article nil t)
  939. (gnus-summary-update-mark
  940. (if unmark
  941. (progn
  942. (setq gnus-newsgroup-downloadable
  943. (delq article gnus-newsgroup-downloadable))
  944. (gnus-article-mark article))
  945. (setq gnus-newsgroup-downloadable
  946. (gnus-add-to-sorted-list gnus-newsgroup-downloadable article))
  947. gnus-downloadable-mark)
  948. 'unread))))
  949. ;;;###autoload
  950. (defun gnus-agent-get-undownloaded-list ()
  951. "Construct list of articles that have not been downloaded."
  952. (let ((gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name)))
  953. (when (set (make-local-variable 'gnus-newsgroup-agentized)
  954. (gnus-agent-method-p gnus-command-method))
  955. (let* ((alist (gnus-agent-load-alist gnus-newsgroup-name))
  956. (headers (sort (mapcar (lambda (h)
  957. (mail-header-number h))
  958. gnus-newsgroup-headers) '<))
  959. (cached (and gnus-use-cache gnus-newsgroup-cached))
  960. (undownloaded (list nil))
  961. (tail-undownloaded undownloaded)
  962. (unfetched (list nil))
  963. (tail-unfetched unfetched))
  964. (while (and alist headers)
  965. (let ((a (caar alist))
  966. (h (car headers)))
  967. (cond ((< a h)
  968. ;; Ignore IDs in the alist that are not being
  969. ;; displayed in the summary.
  970. (setq alist (cdr alist)))
  971. ((> a h)
  972. ;; Headers that are not in the alist should be
  973. ;; fictitious (see nnagent-retrieve-headers); they
  974. ;; imply that this article isn't in the agent.
  975. (gnus-agent-append-to-list tail-undownloaded h)
  976. (gnus-agent-append-to-list tail-unfetched h)
  977. (setq headers (cdr headers)))
  978. ((cdar alist)
  979. (setq alist (cdr alist))
  980. (setq headers (cdr headers))
  981. nil ; ignore already downloaded
  982. )
  983. (t
  984. (setq alist (cdr alist))
  985. (setq headers (cdr headers))
  986. ;; This article isn't in the agent. Check to see
  987. ;; if it is in the cache. If it is, it's been
  988. ;; downloaded.
  989. (while (and cached (< (car cached) a))
  990. (setq cached (cdr cached)))
  991. (unless (equal a (car cached))
  992. (gnus-agent-append-to-list tail-undownloaded a))))))
  993. (while headers
  994. (let ((num (pop headers)))
  995. (gnus-agent-append-to-list tail-undownloaded num)
  996. (gnus-agent-append-to-list tail-unfetched num)))
  997. (setq gnus-newsgroup-undownloaded (cdr undownloaded)
  998. gnus-newsgroup-unfetched (cdr unfetched))))))
  999. (defun gnus-agent-catchup ()
  1000. "Mark as read all unhandled articles.
  1001. An article is unhandled if it is neither cached, nor downloaded, nor
  1002. downloadable."
  1003. (interactive)
  1004. (save-excursion
  1005. (let ((articles gnus-newsgroup-undownloaded))
  1006. (when (or gnus-newsgroup-downloadable
  1007. gnus-newsgroup-cached)
  1008. (setq articles (gnus-sorted-ndifference
  1009. (gnus-sorted-ndifference
  1010. (gnus-copy-sequence articles)
  1011. gnus-newsgroup-downloadable)
  1012. gnus-newsgroup-cached)))
  1013. (while articles
  1014. (gnus-summary-mark-article
  1015. (pop articles) gnus-catchup-mark)))
  1016. (gnus-summary-position-point)))
  1017. (defun gnus-agent-summary-fetch-series ()
  1018. "Fetch the process-marked articles into the Agent."
  1019. (interactive)
  1020. (when gnus-newsgroup-processable
  1021. (setq gnus-newsgroup-downloadable
  1022. (let* ((dl gnus-newsgroup-downloadable)
  1023. (processable (sort (gnus-copy-sequence gnus-newsgroup-processable) '<))
  1024. (gnus-newsgroup-downloadable processable))
  1025. (gnus-agent-summary-fetch-group)
  1026. ;; For each article that I processed that is no longer
  1027. ;; undownloaded, remove its processable mark.
  1028. (mapc #'gnus-summary-remove-process-mark
  1029. (gnus-sorted-ndifference gnus-newsgroup-processable gnus-newsgroup-undownloaded))
  1030. ;; The preceding call to (gnus-agent-summary-fetch-group)
  1031. ;; updated the temporary gnus-newsgroup-downloadable to
  1032. ;; remove each article successfully fetched. Now, I
  1033. ;; update the real gnus-newsgroup-downloadable to only
  1034. ;; include undownloaded articles.
  1035. (gnus-sorted-ndifference dl (gnus-sorted-ndifference processable gnus-newsgroup-undownloaded))))))
  1036. (defun gnus-agent-summary-fetch-group (&optional all)
  1037. "Fetch the downloadable articles in the group.
  1038. Optional arg ALL, if non-nil, means to fetch all articles."
  1039. (interactive "P")
  1040. (let ((articles
  1041. (if all gnus-newsgroup-articles
  1042. gnus-newsgroup-downloadable))
  1043. (gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name))
  1044. fetched-articles)
  1045. (gnus-agent-while-plugged
  1046. (unless articles
  1047. (error "No articles to download"))
  1048. (gnus-agent-with-fetch
  1049. (setq gnus-newsgroup-undownloaded
  1050. (gnus-sorted-ndifference
  1051. gnus-newsgroup-undownloaded
  1052. (setq fetched-articles
  1053. (gnus-agent-fetch-articles
  1054. gnus-newsgroup-name articles)))))
  1055. (save-excursion
  1056. (dolist (article articles)
  1057. (let ((was-marked-downloadable
  1058. (memq article gnus-newsgroup-downloadable)))
  1059. (cond (gnus-agent-mark-unread-after-downloaded
  1060. (setq gnus-newsgroup-downloadable
  1061. (delq article gnus-newsgroup-downloadable))
  1062. (when (and (not (member article gnus-newsgroup-dormant))
  1063. (not (member article gnus-newsgroup-marked)))
  1064. (gnus-summary-mark-article article gnus-unread-mark)))
  1065. (was-marked-downloadable
  1066. (gnus-summary-set-agent-mark article t)))
  1067. (when (gnus-summary-goto-subject article nil t)
  1068. (gnus-summary-update-download-mark article))))))
  1069. fetched-articles))
  1070. (defun gnus-agent-fetch-selected-article ()
  1071. "Fetch the current article as it is selected.
  1072. This can be added to `gnus-select-article-hook' or
  1073. `gnus-mark-article-hook'."
  1074. (let ((gnus-command-method gnus-current-select-method))
  1075. (when (and gnus-plugged (gnus-agent-method-p gnus-command-method))
  1076. (when (gnus-agent-fetch-articles
  1077. gnus-newsgroup-name
  1078. (list gnus-current-article))
  1079. (setq gnus-newsgroup-undownloaded
  1080. (delq gnus-current-article gnus-newsgroup-undownloaded))
  1081. (gnus-summary-update-download-mark gnus-current-article)))))
  1082. ;;;
  1083. ;;; Internal functions
  1084. ;;;
  1085. (defun gnus-agent-synchronize-group-flags (group actions server)
  1086. "Update a plugged group by performing the indicated actions."
  1087. (let* ((gnus-command-method (gnus-server-to-method server))
  1088. (info
  1089. ;; This initializer is required as gnus-request-set-mark
  1090. ;; calls gnus-group-real-name to strip off the host name
  1091. ;; before calling the backend. Now that the backend is
  1092. ;; trying to call gnus-request-set-mark, I have to
  1093. ;; reconstruct the original group name.
  1094. (or (gnus-get-info group)
  1095. (gnus-get-info
  1096. (setq group (gnus-group-full-name
  1097. group gnus-command-method))))))
  1098. (gnus-request-set-mark group actions)
  1099. (when info
  1100. (dolist (action actions)
  1101. (let ((range (nth 0 action))
  1102. (what (nth 1 action))
  1103. (marks (nth 2 action)))
  1104. (dolist (mark marks)
  1105. (cond ((eq mark 'read)
  1106. (gnus-info-set-read
  1107. info
  1108. (funcall (if (eq what 'add)
  1109. 'gnus-range-add
  1110. 'gnus-remove-from-range)
  1111. (gnus-info-read info)
  1112. range))
  1113. (gnus-get-unread-articles-in-group
  1114. info
  1115. (gnus-active (gnus-info-group info))))
  1116. ((memq mark '(tick))
  1117. (let ((info-marks (assoc mark (gnus-info-marks info))))
  1118. (unless info-marks
  1119. (gnus-info-set-marks info (cons (setq info-marks (list mark)) (gnus-info-marks info))))
  1120. (setcdr info-marks (funcall (if (eq what 'add)
  1121. 'gnus-range-add
  1122. 'gnus-remove-from-range)
  1123. (cdr info-marks)
  1124. range))))))))
  1125. ;;Marks can be synchronized at any time by simply toggling from
  1126. ;;unplugged to plugged. If that is what is happening right now, make
  1127. ;;sure that the group buffer is up to date.
  1128. (when (gnus-buffer-live-p gnus-group-buffer)
  1129. (gnus-group-update-group group t)))
  1130. nil))
  1131. (defun gnus-agent-save-active (method &optional groups-p)
  1132. "Sync the agent's active file with the current buffer.
  1133. Pass non-nil for GROUPS-P if the buffer starts out in groups format.
  1134. Regardless, both the file and the buffer end up in active format
  1135. if METHOD is agentized; otherwise the function is a no-op."
  1136. (when (gnus-agent-method-p method)
  1137. (let* ((gnus-command-method method)
  1138. (new (gnus-make-hashtable (count-lines (point-min) (point-max))))
  1139. (file (gnus-agent-lib-file "active")))
  1140. (if groups-p
  1141. (gnus-groups-to-gnus-format nil new)
  1142. (gnus-active-to-gnus-format nil new))
  1143. (gnus-agent-write-active file new)
  1144. (erase-buffer)
  1145. (let ((nnheader-file-coding-system gnus-agent-file-coding-system))
  1146. (nnheader-insert-file-contents file)))))
  1147. (defun gnus-agent-write-active (file new)
  1148. (gnus-make-directory (file-name-directory file))
  1149. (let ((nnmail-active-file-coding-system gnus-agent-file-coding-system))
  1150. ;; The hashtable contains real names of groups. However, do NOT
  1151. ;; add the foreign server prefix as gnus-active-to-gnus-format
  1152. ;; will add it while reading the file.
  1153. (gnus-write-active-file file new nil)))
  1154. ;;;###autoload
  1155. (defun gnus-agent-possibly-alter-active (group active &optional info)
  1156. "Possibly expand a group's active range to include articles
  1157. downloaded into the agent."
  1158. (let* ((gnus-command-method (or gnus-command-method
  1159. (gnus-find-method-for-group group))))
  1160. (when (gnus-agent-method-p gnus-command-method)
  1161. (let* ((local (gnus-agent-get-local group))
  1162. (active-min (or (car active) 0))
  1163. (active-max (or (cdr active) 0))
  1164. (agent-min (or (car local) active-min))
  1165. (agent-max (or (cdr local) active-max)))
  1166. (when (< agent-min active-min)
  1167. (setcar active agent-min))
  1168. (when (> agent-max active-max)
  1169. (setcdr active agent-max))
  1170. (when (and info (< agent-max (- active-min 100)))
  1171. ;; I'm expanding the active range by such a large amount
  1172. ;; that there is a gap of more than 100 articles between the
  1173. ;; last article known to the agent and the first article
  1174. ;; currently available on the server. This gap contains
  1175. ;; articles that have been lost, mark them as read so that
  1176. ;; gnus doesn't waste resources trying to fetch them.
  1177. ;; NOTE: I don't do this for smaller gaps (< 100) as I don't
  1178. ;; want to modify the local file everytime someone restarts
  1179. ;; gnus. The small gap will cause a tiny performance hit
  1180. ;; when gnus tries, and fails, to retrieve the articles.
  1181. ;; Still that should be smaller than opening a buffer,
  1182. ;; printing this list to the buffer, and then writing it to a
  1183. ;; file.
  1184. (let ((read (gnus-info-read info)))
  1185. (gnus-info-set-read
  1186. info
  1187. (gnus-range-add
  1188. read
  1189. (list (cons (1+ agent-max)
  1190. (1- active-min))))))
  1191. ;; Lie about the agent's local range for this group to
  1192. ;; disable the set read each time this server is opened.
  1193. ;; NOTE: Opening this group will restore the valid local
  1194. ;; range but it will also expand the local range to
  1195. ;; encompass the new active range.
  1196. (gnus-agent-set-local group agent-min (1- active-min)))))))
  1197. (defun gnus-agent-save-group-info (method group active)
  1198. "Update a single group's active range in the agent's copy of the server's active file."
  1199. (when (gnus-agent-method-p method)
  1200. (let* ((gnus-command-method (or method gnus-command-method))
  1201. (coding-system-for-write nnheader-file-coding-system)
  1202. (file-name-coding-system nnmail-pathname-coding-system)
  1203. (file (gnus-agent-lib-file "active"))
  1204. oactive-min oactive-max)
  1205. (gnus-make-directory (file-name-directory file))
  1206. (with-temp-file file
  1207. ;; Emacs got problem to match non-ASCII group in multibyte buffer.
  1208. (mm-disable-multibyte)
  1209. (when (file-exists-p file)
  1210. (nnheader-insert-file-contents file)
  1211. (goto-char (point-min))
  1212. (when (re-search-forward
  1213. (concat "^" (regexp-quote group) " ") nil t)
  1214. (save-excursion
  1215. (setq oactive-max (read (current-buffer)) ;; max
  1216. oactive-min (read (current-buffer)))) ;; min
  1217. (gnus-delete-line)))
  1218. (when active
  1219. (insert (format "%S %d %d y\n" (intern group)
  1220. (max (or oactive-max (cdr active)) (cdr active))
  1221. (min (or oactive-min (car active)) (car active))))
  1222. (goto-char (point-max))
  1223. (while (search-backward "\\." nil t)
  1224. (delete-char 1)))))))
  1225. (defun gnus-agent-get-group-info (method group)
  1226. "Get a single group's active range in the agent's copy of the server's active file."
  1227. (when (gnus-agent-method-p method)
  1228. (let* ((gnus-command-method (or method gnus-command-method))
  1229. (coding-system-for-write nnheader-file-coding-system)
  1230. (file-name-coding-system nnmail-pathname-coding-system)
  1231. (file (gnus-agent-lib-file "active"))
  1232. oactive-min oactive-max)
  1233. (gnus-make-directory (file-name-directory file))
  1234. (with-temp-buffer
  1235. ;; Emacs got problem to match non-ASCII group in multibyte buffer.
  1236. (mm-disable-multibyte)
  1237. (when (file-exists-p file)
  1238. (nnheader-insert-file-contents file)
  1239. (goto-char (point-min))
  1240. (when (re-search-forward
  1241. (concat "^" (regexp-quote group) " ") nil t)
  1242. (save-excursion
  1243. (setq oactive-max (read (current-buffer)) ;; max
  1244. oactive-min (read (current-buffer))) ;; min
  1245. (cons oactive-min oactive-max))))))))
  1246. (defvar gnus-agent-decoded-group-names nil
  1247. "Alist of non-ASCII group names and decoded ones.")
  1248. (defun gnus-agent-decoded-group-name (group)
  1249. "Return a decoded group name of GROUP."
  1250. (or (cdr (assoc group gnus-agent-decoded-group-names))
  1251. (if (string-match "[^\000-\177]" group)
  1252. (let ((decoded (gnus-group-decoded-name group)))
  1253. (push (cons group decoded) gnus-agent-decoded-group-names)
  1254. decoded)
  1255. group)))
  1256. (defun gnus-agent-group-path (group)
  1257. "Translate GROUP into a file name."
  1258. ;; NOTE: This is what nnmail-group-pathname does as of Apr 2003.
  1259. ;; The two methods must be kept synchronized, which is why
  1260. ;; gnus-agent-group-pathname was added.
  1261. (setq group
  1262. (nnheader-translate-file-chars
  1263. (nnheader-replace-duplicate-chars-in-string
  1264. (nnheader-replace-chars-in-string
  1265. (gnus-group-real-name (gnus-agent-decoded-group-name group))
  1266. ?/ ?_)
  1267. ?. ?_)))
  1268. (if (or nnmail-use-long-file-names
  1269. (file-directory-p (expand-file-name group (gnus-agent-directory))))
  1270. group
  1271. (nnheader-replace-chars-in-string group ?. ?/)))
  1272. (defun gnus-agent-group-pathname (group)
  1273. "Translate GROUP into a file name."
  1274. ;; nnagent uses nnmail-group-pathname to read articles while
  1275. ;; unplugged. The agent must, therefore, use the same directory
  1276. ;; while plugged.
  1277. (nnmail-group-pathname
  1278. (gnus-group-real-name (gnus-agent-decoded-group-name group))
  1279. (if gnus-command-method
  1280. (gnus-agent-directory)
  1281. (let ((gnus-command-method (gnus-find-method-for-group group)))
  1282. (gnus-agent-directory)))))
  1283. (defun gnus-agent-get-function (method)
  1284. (if (gnus-online method)
  1285. (car method)
  1286. (require 'nnagent)
  1287. 'nnagent))
  1288. (defun gnus-agent-covered-methods ()
  1289. "Return the subset of methods that are covered by the agent."
  1290. (delq nil (mapcar #'gnus-server-to-method gnus-agent-covered-methods)))
  1291. ;;; History functions
  1292. (defun gnus-agent-history-buffer ()
  1293. (cdr (assoc (gnus-agent-method) gnus-agent-history-buffers)))
  1294. (defun gnus-agent-open-history ()
  1295. (save-excursion
  1296. (push (cons (gnus-agent-method)
  1297. (set-buffer (gnus-get-buffer-create
  1298. (format " *Gnus agent %s history*"
  1299. (gnus-agent-method)))))
  1300. gnus-agent-history-buffers)
  1301. (mm-disable-multibyte) ;; everything is binary
  1302. (erase-buffer)
  1303. (insert "\n")
  1304. (let ((file (gnus-agent-lib-file "history")))
  1305. (when (file-exists-p file)
  1306. (nnheader-insert-file-contents file))
  1307. (set (make-local-variable 'gnus-agent-file-name) file))))
  1308. (defun gnus-agent-close-history ()
  1309. (when (gnus-buffer-live-p gnus-agent-current-history)
  1310. (kill-buffer gnus-agent-current-history)
  1311. (setq gnus-agent-history-buffers
  1312. (delq (assoc (gnus-agent-method) gnus-agent-history-buffers)
  1313. gnus-agent-history-buffers))))
  1314. ;;;
  1315. ;;; Fetching
  1316. ;;;
  1317. (defun gnus-agent-fetch-articles (group articles)
  1318. "Fetch ARTICLES from GROUP and put them into the Agent."
  1319. (when (and articles
  1320. (gnus-online (gnus-group-method group)))
  1321. (gnus-agent-load-alist group)
  1322. (let* ((alist gnus-agent-article-alist)
  1323. (headers (if (< (length articles) 2) nil gnus-newsgroup-headers))
  1324. (selected-sets (list nil))
  1325. (current-set-size 0)
  1326. article
  1327. header-number)
  1328. ;; Check each article
  1329. (while (setq article (pop articles))
  1330. ;; Skip alist entries preceding this article
  1331. (while (> article (or (caar alist) (1+ article)))
  1332. (setq alist (cdr alist)))
  1333. ;; Prune off articles that we have already fetched.
  1334. (unless (and (eq article (caar alist))
  1335. (cdar alist))
  1336. ;; Skip headers preceding this article
  1337. (while (> article
  1338. (setq header-number
  1339. (let* ((header (car headers)))
  1340. (if header
  1341. (mail-header-number header)
  1342. (1+ article)))))
  1343. (setq headers (cdr headers)))
  1344. ;; Add this article to the current set
  1345. (setcar selected-sets (cons article (car selected-sets)))
  1346. ;; Update the set size, when the set is too large start a
  1347. ;; new one. I do this after adding the article as I want at
  1348. ;; least one article in each set.
  1349. (when (< gnus-agent-max-fetch-size
  1350. (setq current-set-size
  1351. (+ current-set-size
  1352. (if (= header-number article)
  1353. (let ((char-size (mail-header-chars
  1354. (car headers))))
  1355. (if (<= char-size 0)
  1356. ;; The char size was missing/invalid,
  1357. ;; assume a worst-case situation of
  1358. ;; 65 char/line. If the line count
  1359. ;; is missing, arbitrarily assume a
  1360. ;; size of 1000 characters.
  1361. (max (* 65 (mail-header-lines
  1362. (car headers)))
  1363. 1000)
  1364. char-size))
  1365. 0))))
  1366. (setcar selected-sets (nreverse (car selected-sets)))
  1367. (setq selected-sets (cons nil selected-sets)
  1368. current-set-size 0))))
  1369. (when (or (cdr selected-sets) (car selected-sets))
  1370. (let* ((fetched-articles (list nil))
  1371. (tail-fetched-articles fetched-articles)
  1372. (dir (gnus-agent-group-pathname group))
  1373. (date (time-to-days (current-time)))
  1374. (case-fold-search t)
  1375. pos crosses
  1376. (file-name-coding-system nnmail-pathname-coding-system))
  1377. (setcar selected-sets (nreverse (car selected-sets)))
  1378. (setq selected-sets (nreverse selected-sets))
  1379. (gnus-make-directory dir)
  1380. (gnus-message 7 "Fetching articles for %s..."
  1381. (gnus-agent-decoded-group-name group))
  1382. (unwind-protect
  1383. (while (setq articles (pop selected-sets))
  1384. ;; Fetch the articles from the backend.
  1385. (if (gnus-check-backend-function 'retrieve-articles group)
  1386. (setq pos (gnus-retrieve-articles articles group))
  1387. (with-temp-buffer
  1388. (let (article)
  1389. (while (setq article (pop articles))
  1390. (gnus-message 10 "Fetching article %s for %s..."
  1391. article
  1392. (gnus-agent-decoded-group-name group))
  1393. (when (or
  1394. (gnus-backlog-request-article group article
  1395. nntp-server-buffer)
  1396. (gnus-request-article article group))
  1397. (goto-char (point-max))
  1398. (push (cons article (point)) pos)
  1399. (insert-buffer-substring nntp-server-buffer)))
  1400. (copy-to-buffer
  1401. nntp-server-buffer (point-min) (point-max))
  1402. (setq pos (nreverse pos)))))
  1403. ;; Then save these articles into the Agent.
  1404. (with-current-buffer nntp-server-buffer
  1405. (while pos
  1406. (narrow-to-region (cdar pos) (or (cdadr pos) (point-max)))
  1407. (goto-char (point-min))
  1408. (unless (eobp) ;; Don't save empty articles.
  1409. (when (search-forward "\n\n" nil t)
  1410. (when (search-backward "\nXrefs: " nil t)
  1411. ;; Handle cross posting.
  1412. (goto-char (match-end 0)) ; move to end of header name
  1413. (skip-chars-forward "^ ") ; skip server name
  1414. (skip-chars-forward " ")
  1415. (setq crosses nil)
  1416. (while (looking-at "\\([^: \n]+\\):\\([0-9]+\\) *")
  1417. (push (cons (buffer-substring (match-beginning 1)
  1418. (match-end 1))
  1419. (string-to-number
  1420. (buffer-substring (match-beginning 2)
  1421. (match-end 2))))
  1422. crosses)
  1423. (goto-char (match-end 0)))
  1424. (gnus-agent-crosspost crosses (caar pos) date)))
  1425. (goto-char (point-min))
  1426. (let ((coding-system-for-write
  1427. gnus-agent-file-coding-system))
  1428. (write-region (point-min) (point-max)
  1429. (concat dir (number-to-string (caar pos)))
  1430. nil 'silent))
  1431. (gnus-agent-append-to-list
  1432. tail-fetched-articles (caar pos)))
  1433. (widen)
  1434. (setq pos (cdr pos)))))
  1435. (gnus-agent-save-alist group (cdr fetched-articles) date)
  1436. (gnus-agent-update-files-total-fetched-for group (cdr fetched-articles))
  1437. (gnus-message 7 ""))
  1438. (cdr fetched-articles))))))
  1439. (defun gnus-agent-unfetch-articles (group articles)
  1440. "Delete ARTICLES that were fetched from GROUP into the agent."
  1441. (when articles
  1442. (gnus-agent-with-refreshed-group
  1443. group
  1444. (gnus-agent-load-alist group)
  1445. (let* ((alist (cons nil gnus-agent-article-alist))
  1446. (articles (sort articles #'<))
  1447. (next-possibility alist)
  1448. (delete-this (pop articles)))
  1449. (while (and (cdr next-possibility) delete-this)
  1450. (let ((have-this (caar (cdr next-possibility))))
  1451. (cond
  1452. ((< delete-this have-this)
  1453. (setq delete-this (pop articles)))
  1454. ((= delete-this have-this)
  1455. (let ((timestamp (cdar (cdr next-possibility))))
  1456. (when timestamp
  1457. (let* ((file-name (concat (gnus-agent-group-pathname group)
  1458. (number-to-string have-this)))
  1459. (size-file
  1460. (float (or (and gnus-agent-total-fetched-hashtb
  1461. (nth 7 (file-attributes file-name)))
  1462. 0)))
  1463. (file-name-coding-system
  1464. nnmail-pathname-coding-system))
  1465. (delete-file file-name)
  1466. (gnus-agent-update-files-total-fetched-for
  1467. group (- size-file)))))
  1468. (setcdr next-possibility (cddr next-possibility)))
  1469. (t
  1470. (setq next-possibility (cdr next-possibility))))))
  1471. (setq gnus-agent-article-alist (cdr alist))
  1472. (gnus-agent-save-alist group)))))
  1473. (defun gnus-agent-crosspost (crosses article &optional date)
  1474. (setq date (or date t))
  1475. (let (gnus-agent-article-alist group alist beg end)
  1476. (with-current-buffer gnus-agent-overview-buffer
  1477. (when (nnheader-find-nov-line article)
  1478. (forward-word 1)
  1479. (setq beg (point))
  1480. (setq end (progn (forward-line 1) (point)))))
  1481. (while crosses
  1482. (setq group (caar crosses))
  1483. (unless (setq alist (assoc group gnus-agent-group-alist))
  1484. (push (setq alist (list group (gnus-agent-load-alist (caar crosses))))
  1485. gnus-agent-group-alist))
  1486. (setcdr alist (cons (cons (cdar crosses) date) (cdr alist)))
  1487. (with-current-buffer (gnus-get-buffer-create
  1488. (format " *Gnus agent overview %s*"group))
  1489. (when (= (point-max) (point-min))
  1490. (push (cons group (current-buffer)) gnus-agent-buffer-alist)
  1491. (ignore-errors
  1492. (let ((file-name-coding-system nnmail-pathname-coding-system))
  1493. (nnheader-insert-file-contents
  1494. (gnus-agent-article-name ".overview" group)))))
  1495. (nnheader-find-nov-line (string-to-number (cdar crosses)))
  1496. (insert (string-to-number (cdar crosses)))
  1497. (insert-buffer-substring gnus-agent-overview-buffer beg end)
  1498. (gnus-agent-check-overview-buffer))
  1499. (setq crosses (cdr crosses)))))
  1500. (defun gnus-agent-backup-overview-buffer ()
  1501. (when gnus-newsgroup-name
  1502. (let ((root (gnus-agent-article-name ".overview" gnus-newsgroup-name))
  1503. (cnt 0)
  1504. name
  1505. (file-name-coding-system nnmail-pathname-coding-system))
  1506. (while (file-exists-p
  1507. (setq name (concat root "~"
  1508. (int-to-string (setq cnt (1+ cnt))) "~"))))
  1509. (write-region (point-min) (point-max) name nil 'no-msg)
  1510. (gnus-message 1 "Created backup copy of overview in %s." name)))
  1511. t)
  1512. (defun gnus-agent-check-overview-buffer (&optional buffer)
  1513. "Check the overview file given for sanity.
  1514. In particular, checks that the file is sorted by article number
  1515. and that there are no duplicates."
  1516. (let ((prev-num -1)
  1517. (backed-up nil))
  1518. (save-excursion
  1519. (when buffer
  1520. (set-buffer buffer))
  1521. (save-restriction
  1522. (widen)
  1523. (goto-char (point-min))
  1524. (while (< (point) (point-max))
  1525. (let ((p (point))
  1526. (cur (condition-case nil
  1527. (read (current-buffer))
  1528. (error nil))))
  1529. (cond
  1530. ((or (not (integerp cur))
  1531. (not (eq (char-after) ?\t)))
  1532. (or backed-up
  1533. (setq backed-up (gnus-agent-backup-overview-buffer)))
  1534. (gnus-message 1
  1535. "Overview buffer contains garbage `%s'."
  1536. (buffer-substring
  1537. p (point-at-eol))))
  1538. ((= cur prev-num)
  1539. (or backed-up
  1540. (setq backed-up (gnus-agent-backup-overview-buffer)))
  1541. (gnus-message 1
  1542. "Duplicate overview line for %d" cur)
  1543. (delete-region p (progn (forward-line 1) (point))))
  1544. ((< cur prev-num)
  1545. (or backed-up
  1546. (setq backed-up (gnus-agent-backup-overview-buffer)))
  1547. (gnus-message 1 "Overview buffer not sorted!")
  1548. (sort-numeric-fields 1 (point-min) (point-max))
  1549. (goto-char (point-min))
  1550. (setq prev-num -1))
  1551. (t
  1552. (setq prev-num cur)))
  1553. (forward-line 1)))))))
  1554. (defun gnus-agent-flush-server (&optional server-or-method)
  1555. "Flush all agent index files for every subscribed group within
  1556. the given SERVER-OR-METHOD. When called with nil, the current
  1557. value of gnus-command-method identifies the server."
  1558. (let* ((gnus-command-method (if server-or-method
  1559. (gnus-server-to-method server-or-method)
  1560. gnus-command-method))
  1561. (alist gnus-newsrc-alist))
  1562. (while alist
  1563. (let ((entry (pop alist)))
  1564. (when (gnus-methods-equal-p gnus-command-method (gnus-info-method entry))
  1565. (gnus-agent-flush-group (gnus-info-group entry)))))))
  1566. (defun gnus-agent-flush-group (group)
  1567. "Flush the agent's index files such that the GROUP no longer
  1568. appears to have any local content. The actual content, the
  1569. article files, may then be deleted using gnus-agent-expire-group.
  1570. If flushing was a mistake, the gnus-agent-regenerate-group method
  1571. provides an undo mechanism by reconstructing the index files from
  1572. the article files."
  1573. (interactive (list (gnus-agent-read-group)))
  1574. (let* ((gnus-command-method (or gnus-command-method
  1575. (gnus-find-method-for-group group)))
  1576. (overview (gnus-agent-article-name ".overview" group))
  1577. (agentview (gnus-agent-article-name ".agentview" group))
  1578. (file-name-coding-system nnmail-pathname-coding-system))
  1579. (if (file-exists-p overview)
  1580. (delete-file overview))
  1581. (if (file-exists-p agentview)
  1582. (delete-file agentview))
  1583. (gnus-agent-update-view-total-fetched-for group nil gnus-command-method)
  1584. (gnus-agent-update-view-total-fetched-for group t gnus-command-method)
  1585. ;(gnus-agent-set-local group nil nil)
  1586. ;(gnus-agent-save-local t)
  1587. (gnus-agent-save-group-info nil group nil)))
  1588. (defun gnus-agent-flush-cache ()
  1589. "Flush the agent's index files such that the group no longer
  1590. appears to have any local content. The actual content, the
  1591. article files, is then deleted using gnus-agent-expire-group. The
  1592. gnus-agent-regenerate-group method provides an undo mechanism by
  1593. reconstructing the index files from the article files."
  1594. (interactive)
  1595. (save-excursion
  1596. (let ((file-name-coding-system nnmail-pathname-coding-system))
  1597. (while gnus-agent-buffer-alist
  1598. (set-buffer (cdar gnus-agent-buffer-alist))
  1599. (let ((coding-system-for-write gnus-agent-file-coding-system))
  1600. (write-region (point-min) (point-max)
  1601. (gnus-agent-article-name ".overview"
  1602. (caar gnus-agent-buffer-alist))
  1603. nil 'silent))
  1604. (setq gnus-agent-buffer-alist (cdr gnus-agent-buffer-alist)))
  1605. (while gnus-agent-group-alist
  1606. (with-temp-file (gnus-agent-article-name
  1607. ".agentview" (caar gnus-agent-group-alist))
  1608. (princ (cdar gnus-agent-group-alist))
  1609. (insert "\n")
  1610. (princ 1 (current-buffer))
  1611. (insert "\n"))
  1612. (setq gnus-agent-group-alist (cdr gnus-agent-group-alist))))))
  1613. ;;;###autoload
  1614. (defun gnus-agent-find-parameter (group symbol)
  1615. "Search for GROUPs SYMBOL in the group's parameters, the group's
  1616. topic parameters, the group's category, or the customizable
  1617. variables. Returns the first non-nil value found."
  1618. (or (gnus-group-find-parameter group symbol t)
  1619. (gnus-group-parameter-value (cdr (gnus-group-category group)) symbol t)
  1620. (symbol-value
  1621. (cdr
  1622. (assq symbol
  1623. '((agent-short-article . gnus-agent-short-article)
  1624. (agent-long-article . gnus-agent-long-article)
  1625. (agent-low-score . gnus-agent-low-score)
  1626. (agent-high-score . gnus-agent-high-score)
  1627. (agent-days-until-old . gnus-agent-expire-days)
  1628. (agent-enable-expiration
  1629. . gnus-agent-enable-expiration)
  1630. (agent-predicate . gnus-agent-predicate)))))))
  1631. (defun gnus-agent-fetch-headers (group)
  1632. "Fetch interesting headers into the agent. The group's overview
  1633. file will be updated to include the headers while a list of available
  1634. article numbers will be returned."
  1635. (let* ((fetch-all (and gnus-agent-consider-all-articles
  1636. ;; Do not fetch all headers if the predicate
  1637. ;; implies that we only consider unread articles.
  1638. (not (gnus-predicate-implies-unread
  1639. (gnus-agent-find-parameter group
  1640. 'agent-predicate)))))
  1641. (articles (if fetch-all
  1642. (if gnus-newsgroup-maximum-articles
  1643. (let ((active (gnus-active group)))
  1644. (gnus-uncompress-range
  1645. (cons (max (car active)
  1646. (- (cdr active)
  1647. gnus-newsgroup-maximum-articles
  1648. -1))
  1649. (cdr active))))
  1650. (gnus-uncompress-range (gnus-active group)))
  1651. (gnus-list-of-unread-articles group)))
  1652. (gnus-decode-encoded-word-function 'identity)
  1653. (gnus-decode-encoded-address-function 'identity)
  1654. (file (gnus-agent-article-name ".overview" group))
  1655. (file-name-coding-system nnmail-pathname-coding-system))
  1656. (unless fetch-all
  1657. ;; Add articles with marks to the list of article headers we want to
  1658. ;; fetch. Don't fetch articles solely on the basis of a recent or seen
  1659. ;; mark, but do fetch recent or seen articles if they have other, more
  1660. ;; interesting marks. (We have to fetch articles with boring marks
  1661. ;; because otherwise the agent will remove their marks.)
  1662. (dolist (arts (gnus-info-marks (gnus-get-info group)))
  1663. (unless (memq (car arts) '(seen recent killed cache))
  1664. (setq articles (gnus-range-add articles (cdr arts)))))
  1665. (setq articles (sort (gnus-uncompress-sequence articles) '<)))
  1666. ;; At this point, I have the list of articles to consider for
  1667. ;; fetching. This is the list that I'll return to my caller. Some
  1668. ;; of these articles may have already been fetched. That's OK as
  1669. ;; the fetch article code will filter those out. Internally, I'll
  1670. ;; filter this list to just those articles whose headers need to
  1671. ;; be fetched.
  1672. (let ((articles articles))
  1673. ;; Remove known articles.
  1674. (when (and (or gnus-agent-cache
  1675. (not gnus-plugged))
  1676. (gnus-agent-load-alist group))
  1677. ;; Remove articles marked as downloaded.
  1678. (if fetch-all
  1679. ;; I want to fetch all headers in the active range.
  1680. ;; Therefore, exclude only those headers that are in the
  1681. ;; article alist.
  1682. ;; NOTE: This is probably NOT what I want to do after
  1683. ;; agent expiration in this group.
  1684. (setq articles (gnus-agent-uncached-articles articles group))
  1685. ;; I want to only fetch those headers that have never been
  1686. ;; fetched. Therefore, exclude all headers that are, or
  1687. ;; WERE, in the article alist.
  1688. (let ((low (1+ (caar (last gnus-agent-article-alist))))
  1689. (high (cdr (gnus-active group))))
  1690. ;; Low can be greater than High when the same group is
  1691. ;; fetched twice in the same session {The first fetch will
  1692. ;; fill the article alist such that (last
  1693. ;; gnus-agent-article-alist) equals (cdr (gnus-active
  1694. ;; group))}. The addition of one(the 1+ above) then
  1695. ;; forces Low to be greater than High. When this happens,
  1696. ;; gnus-list-range-intersection returns nil which
  1697. ;; indicates that no headers need to be fetched. -- Kevin
  1698. (setq articles (gnus-list-range-intersection
  1699. articles (list (cons low high)))))))
  1700. (when articles
  1701. (gnus-message
  1702. 10 "gnus-agent-fetch-headers: undownloaded articles are `%s'"
  1703. (gnus-compress-sequence articles t)))
  1704. (with-current-buffer nntp-server-buffer
  1705. (if articles
  1706. (progn
  1707. (gnus-message 8 "Fetching headers for %s..."
  1708. (gnus-agent-decoded-group-name group))
  1709. ;; Fetch them.
  1710. (gnus-make-directory (nnheader-translate-file-chars
  1711. (file-name-directory file) t))
  1712. (unless (eq 'nov (gnus-retrieve-headers articles group))
  1713. (nnvirtual-convert-headers))
  1714. (gnus-agent-check-overview-buffer)
  1715. ;; Move these headers to the overview buffer so that
  1716. ;; gnus-agent-braid-nov can merge them with the contents
  1717. ;; of FILE.
  1718. (copy-to-buffer
  1719. gnus-agent-overview-buffer (point-min) (point-max))
  1720. ;; NOTE: Call g-a-brand-nov even when the file does not
  1721. ;; exist. As a minimum, it will validate the article
  1722. ;; numbers already in the buffer.
  1723. (gnus-agent-braid-nov articles file)
  1724. (let ((coding-system-for-write
  1725. gnus-agent-file-coding-system))
  1726. (gnus-agent-check-overview-buffer)
  1727. (write-region (point-min) (point-max) file nil 'silent))
  1728. (gnus-agent-update-view-total-fetched-for group t)
  1729. (gnus-agent-save-alist group articles nil)
  1730. articles)
  1731. (ignore-errors
  1732. (erase-buffer)
  1733. (nnheader-insert-file-contents file)))))
  1734. articles))
  1735. (defsubst gnus-agent-read-article-number ()
  1736. "Reads the article number at point. Returns nil when a valid article number can not be read."
  1737. ;; It is unfortunate but the read function quietly overflows
  1738. ;; integer. As a result, I have to use string operations to test
  1739. ;; for overflow BEFORE calling read.
  1740. (when (looking-at "[0-9]+\t")
  1741. (let ((len (- (match-end 0) (match-beginning 0))))
  1742. (cond ((< len 9)
  1743. (read (current-buffer)))
  1744. ((= len 9)
  1745. ;; Many 9 digit base-10 numbers can be represented in a 27-bit int
  1746. ;; Back convert from int to string to ensure that this is one of them.
  1747. (let* ((str1 (buffer-substring (match-beginning 0) (1- (match-end 0))))
  1748. (num (read (current-buffer)))
  1749. (str2 (int-to-string num)))
  1750. (when (equal str1 str2)
  1751. num)))))))
  1752. (defsubst gnus-agent-copy-nov-line (article)
  1753. "Copy the indicated ARTICLE from the overview buffer to the nntp server buffer."
  1754. (let (art b e)
  1755. (set-buffer gnus-agent-overview-buffer)
  1756. (while (and (not (eobp))
  1757. (or (not (setq art (gnus-agent-read-article-number)))
  1758. (< art article)))
  1759. (forward-line 1))
  1760. (beginning-of-line)
  1761. (if (or (eobp)
  1762. (not (eq article art)))
  1763. (set-buffer nntp-server-buffer)
  1764. (setq b (point))
  1765. (setq e (progn (forward-line 1) (point)))
  1766. (set-buffer nntp-server-buffer)
  1767. (insert-buffer-substring gnus-agent-overview-buffer b e))))
  1768. (defun gnus-agent-braid-nov (articles file)
  1769. "Merge agent overview data with given file.
  1770. Takes unvalidated headers for ARTICLES from
  1771. `gnus-agent-overview-buffer' and validated headers from the given
  1772. FILE and places the combined valid headers into
  1773. `nntp-server-buffer'. This function can be used, when file
  1774. doesn't exist, to valid the overview buffer."
  1775. (let (start last)
  1776. (set-buffer gnus-agent-overview-buffer)
  1777. (goto-char (point-min))
  1778. (set-buffer nntp-server-buffer)
  1779. (erase-buffer)
  1780. (when (file-exists-p file)
  1781. (nnheader-insert-file-contents file))
  1782. (goto-char (point-max))
  1783. (forward-line -1)
  1784. (unless (or (= (point-min) (point-max))
  1785. (< (setq last (read (current-buffer))) (car articles)))
  1786. ;; Old and new overlap -- We do it the hard way.
  1787. (when (nnheader-find-nov-line (car articles))
  1788. ;; Replacing existing NOV entry
  1789. (delete-region (point) (progn (forward-line 1) (point))))
  1790. (gnus-agent-copy-nov-line (pop articles))
  1791. (ignore-errors
  1792. (while articles
  1793. (while (let ((art (read (current-buffer))))
  1794. (cond ((< art (car articles))
  1795. (forward-line 1)
  1796. t)
  1797. ((= art (car articles))
  1798. (beginning-of-line)
  1799. (delete-region
  1800. (point) (progn (forward-line 1) (point)))
  1801. nil)
  1802. (t
  1803. (beginning-of-line)
  1804. nil))))
  1805. (gnus-agent-copy-nov-line (pop articles)))))
  1806. (goto-char (point-max))
  1807. ;; Append the remaining lines
  1808. (when articles
  1809. (when last
  1810. (set-buffer gnus-agent-overview-buffer)
  1811. (setq start (point))
  1812. (set-buffer nntp-server-buffer))
  1813. (let ((p (point)))
  1814. (insert-buffer-substring gnus-agent-overview-buffer start)
  1815. (goto-char p))
  1816. (setq last (or last -134217728))
  1817. (while (catch 'problems
  1818. (let (sort art)
  1819. (while (not (eobp))
  1820. (setq art (gnus-agent-read-article-number))
  1821. (cond ((not art)
  1822. ;; Bad art num - delete this line
  1823. (beginning-of-line)
  1824. (delete-region (point) (progn (forward-line 1) (point))))
  1825. ((< art last)
  1826. ;; Art num out of order - enable sort
  1827. (setq sort t)
  1828. (forward-line 1))
  1829. ((= art last)
  1830. ;; Bad repeat of art number - delete this line
  1831. (beginning-of-line)
  1832. (delete-region (point) (progn (forward-line 1) (point))))
  1833. (t
  1834. ;; Good art num
  1835. (setq last art)
  1836. (forward-line 1))))
  1837. (when sort
  1838. ;; something is seriously wrong as we simply shouldn't see out-of-order data.
  1839. ;; First, we'll fix the sort.
  1840. (sort-numeric-fields 1 (point-min) (point-max))
  1841. ;; but now we have to consider that we may have duplicate rows...
  1842. ;; so reset to beginning of file
  1843. (goto-char (point-min))
  1844. (setq last -134217728)
  1845. ;; and throw a code that restarts this scan
  1846. (throw 'problems t))
  1847. nil))))))
  1848. ;; Keeps the compiler from warning about the free variable in
  1849. ;; gnus-agent-read-agentview.
  1850. (defvar gnus-agent-read-agentview)
  1851. (defun gnus-agent-load-alist (group)
  1852. "Load the article-state alist for GROUP."
  1853. ;; Bind free variable that's used in `gnus-agent-read-agentview'.
  1854. (let* ((gnus-agent-read-agentview group)
  1855. (file-name-coding-system nnmail-pathname-coding-system)
  1856. (agentview (gnus-agent-article-name ".agentview" group)))
  1857. (setq gnus-agent-article-alist
  1858. (and (file-exists-p agentview)
  1859. (gnus-cache-file-contents
  1860. agentview
  1861. 'gnus-agent-file-loading-cache
  1862. 'gnus-agent-read-agentview)))))
  1863. (defun gnus-agent-read-agentview (file)
  1864. "Load FILE and do a `read' there."
  1865. (with-temp-buffer
  1866. (condition-case nil
  1867. (progn
  1868. (nnheader-insert-file-contents file)
  1869. (goto-char (point-min))
  1870. (let ((alist (read (current-buffer)))
  1871. (version (condition-case nil (read (current-buffer))
  1872. (end-of-file 0)))
  1873. changed-version)
  1874. (cond
  1875. ((= version 0)
  1876. (let ((inhibit-quit t)
  1877. entry)
  1878. (gnus-agent-open-history)
  1879. (set-buffer (gnus-agent-history-buffer))
  1880. (goto-char (point-min))
  1881. (while (not (eobp))
  1882. (if (and (looking-at
  1883. "[^\t\n]+\t\\([0-9]+\\)\t\\([^ \n]+\\) \\([0-9]+\\)")
  1884. (string= (match-string 2)
  1885. gnus-agent-read-agentview)
  1886. (setq entry (assoc (string-to-number (match-string 3)) alist)))
  1887. (setcdr entry (string-to-number (match-string 1))))
  1888. (forward-line 1))
  1889. (gnus-agent-close-history)
  1890. (setq changed-version t)))
  1891. ((= version 1)
  1892. (setq changed-version (not (= 1 gnus-agent-article-alist-save-format))))
  1893. ((= version 2)
  1894. (let (state sequence uncomp)
  1895. (while alist
  1896. (setq state (caar alist)
  1897. sequence (inline (gnus-uncompress-range (cdar alist)))
  1898. alist (cdr alist))
  1899. (while sequence
  1900. (push (cons (pop sequence) state) uncomp)))
  1901. (setq alist (sort uncomp 'car-less-than-car)))
  1902. (setq changed-version (not (= 2 gnus-agent-article-alist-save-format)))))
  1903. (when changed-version
  1904. (let ((gnus-agent-article-alist alist))
  1905. (gnus-agent-save-alist gnus-agent-read-agentview)))
  1906. alist))
  1907. ((end-of-file file-error)
  1908. ;; The agentview file is missing.
  1909. (condition-case nil
  1910. ;; If the agent directory exists, attempt to perform a brute-force
  1911. ;; reconstruction of its contents.
  1912. (let* (alist
  1913. (file-name-coding-system nnmail-pathname-coding-system)
  1914. (file-attributes (directory-files-and-attributes
  1915. (gnus-agent-article-name ""
  1916. gnus-agent-read-agentview) nil "^[0-9]+$" t)))
  1917. (while file-attributes
  1918. (let ((fa (pop file-attributes)))
  1919. (unless (nth 1 fa)
  1920. (push (cons (string-to-number (nth 0 fa)) (time-to-days (nth 5 fa))) alist))))
  1921. alist)
  1922. (file-error nil))))))
  1923. (defun gnus-agent-save-alist (group &optional articles state)
  1924. "Save the article-state alist for GROUP."
  1925. (let* ((file-name-coding-system nnmail-pathname-coding-system)
  1926. (prev (cons nil gnus-agent-article-alist))
  1927. (all prev)
  1928. print-level print-length article)
  1929. (while (setq article (pop articles))
  1930. (while (and (cdr prev)
  1931. (< (caadr prev) article))
  1932. (setq prev (cdr prev)))
  1933. (cond
  1934. ((not (cdr prev))
  1935. (setcdr prev (list (cons article state))))
  1936. ((> (caadr prev) article)
  1937. (setcdr prev (cons (cons article state) (cdr prev))))
  1938. ((= (caadr prev) article)
  1939. (setcdr (cadr prev) state)))
  1940. (setq prev (cdr prev)))
  1941. (setq gnus-agent-article-alist (cdr all))
  1942. (gnus-agent-set-local group
  1943. (caar gnus-agent-article-alist)
  1944. (caar (last gnus-agent-article-alist)))
  1945. (gnus-make-directory (gnus-agent-article-name "" group))
  1946. (with-temp-file (gnus-agent-article-name ".agentview" group)
  1947. (cond ((eq gnus-agent-article-alist-save-format 1)
  1948. (princ gnus-agent-article-alist (current-buffer)))
  1949. ((eq gnus-agent-article-alist-save-format 2)
  1950. (let ((alist gnus-agent-article-alist)
  1951. article-id day-of-download comp-list compressed)
  1952. (while alist
  1953. (setq article-id (caar alist)
  1954. day-of-download (cdar alist)
  1955. comp-list (assq day-of-download compressed)
  1956. alist (cdr alist))
  1957. (if comp-list
  1958. (setcdr comp-list (cons article-id (cdr comp-list)))
  1959. (push (list day-of-download article-id) compressed)))
  1960. (setq alist compressed)
  1961. (while alist
  1962. (setq comp-list (pop alist))
  1963. (setcdr comp-list
  1964. (gnus-compress-sequence (nreverse (cdr comp-list)))))
  1965. (princ compressed (current-buffer)))))
  1966. (insert "\n")
  1967. (princ gnus-agent-article-alist-save-format (current-buffer))
  1968. (insert "\n"))
  1969. (gnus-agent-update-view-total-fetched-for group nil)))
  1970. (defvar gnus-agent-article-local nil)
  1971. (defvar gnus-agent-article-local-times nil)
  1972. (defvar gnus-agent-file-loading-local nil)
  1973. (defun gnus-agent-load-local (&optional method)
  1974. "Load the METHOD'S local file. The local file contains min/max
  1975. article counts for each of the method's subscribed groups."
  1976. (let ((gnus-command-method (or method gnus-command-method)))
  1977. (when (or (null gnus-agent-article-local-times)
  1978. (zerop gnus-agent-article-local-times)
  1979. (not (gnus-methods-equal-p
  1980. gnus-command-method
  1981. (symbol-value (intern "+method" gnus-agent-article-local)))))
  1982. (setq gnus-agent-article-local
  1983. (gnus-cache-file-contents
  1984. (gnus-agent-lib-file "local")
  1985. 'gnus-agent-file-loading-local
  1986. 'gnus-agent-read-and-cache-local))
  1987. (when gnus-agent-article-local-times
  1988. (incf gnus-agent-article-local-times)))
  1989. gnus-agent-article-local))
  1990. (defun gnus-agent-read-and-cache-local (file)
  1991. "Load and read FILE then bind its contents to
  1992. gnus-agent-article-local. If that variable had `dirty' (also known as
  1993. modified) original contents, they are first saved to their own file."
  1994. (if (and gnus-agent-article-local
  1995. (symbol-value (intern "+dirty" gnus-agent-article-local)))
  1996. (gnus-agent-save-local))
  1997. (gnus-agent-read-local file))
  1998. (defun gnus-agent-read-local (file)
  1999. "Load FILE and do a `read' there."
  2000. (let ((my-obarray (gnus-make-hashtable (count-lines (point-min)
  2001. (point-max))))
  2002. (line 1))
  2003. (with-temp-buffer
  2004. (condition-case nil
  2005. (let ((nnheader-file-coding-system gnus-agent-file-coding-system))
  2006. (nnheader-insert-file-contents file))
  2007. (file-error))
  2008. (goto-char (point-min))
  2009. ;; Skip any comments at the beginning of the file (the only place where they may appear)
  2010. (while (= (following-char) ?\;)
  2011. (forward-line 1)
  2012. (setq line (1+ line)))
  2013. (while (not (eobp))
  2014. (condition-case err
  2015. (let (group
  2016. min
  2017. max
  2018. (cur (current-buffer))
  2019. (obarray my-obarray))
  2020. (setq group (read cur)
  2021. min (read cur)
  2022. max (read cur))
  2023. (when (stringp group)
  2024. (setq group (intern group my-obarray)))
  2025. ;; NOTE: The '+ 0' ensure that min and max are both numerics.
  2026. (set group (cons (+ 0 min) (+ 0 max))))
  2027. (error
  2028. (gnus-message 3 "Warning - invalid agent local: %s on line %d: %s"
  2029. file line (error-message-string err))))
  2030. (forward-line 1)
  2031. (setq line (1+ line))))
  2032. (set (intern "+dirty" my-obarray) nil)
  2033. (set (intern "+method" my-obarray) gnus-command-method)
  2034. my-obarray))
  2035. (defun gnus-agent-save-local (&optional force)
  2036. "Save gnus-agent-article-local under it method's agent.lib directory."
  2037. (let ((my-obarray gnus-agent-article-local))
  2038. (when (and my-obarray
  2039. (or force (symbol-value (intern "+dirty" my-obarray))))
  2040. (let* ((gnus-command-method (symbol-value (intern "+method" my-obarray)))
  2041. ;; NOTE: gnus-command-method is used within gnus-agent-lib-file.
  2042. (dest (gnus-agent-lib-file "local")))
  2043. (gnus-make-directory (gnus-agent-lib-file ""))
  2044. (let ((coding-system-for-write gnus-agent-file-coding-system)
  2045. (file-name-coding-system nnmail-pathname-coding-system))
  2046. (with-temp-file dest
  2047. (let ((gnus-command-method (symbol-value (intern "+method" my-obarray)))
  2048. print-level print-length
  2049. (standard-output (current-buffer)))
  2050. (mapatoms (lambda (symbol)
  2051. (cond ((not (boundp symbol))
  2052. nil)
  2053. ((member (symbol-name symbol) '("+dirty" "+method"))
  2054. nil)
  2055. (t
  2056. (let ((range (symbol-value symbol)))
  2057. (when range
  2058. (prin1 symbol)
  2059. (princ " ")
  2060. (princ (car range))
  2061. (princ " ")
  2062. (princ (cdr range))
  2063. (princ "\n"))))))
  2064. my-obarray))))))))
  2065. (defun gnus-agent-get-local (group &optional gmane method)
  2066. (let* ((gmane (or gmane (gnus-group-real-name group)))
  2067. (gnus-command-method (or method (gnus-find-method-for-group group)))
  2068. (local (gnus-agent-load-local))
  2069. (symb (intern gmane local))
  2070. (minmax (and (boundp symb) (symbol-value symb))))
  2071. (unless minmax
  2072. ;; Bind these so that gnus-agent-load-alist doesn't change the
  2073. ;; current alist (i.e. gnus-agent-article-alist)
  2074. (let* ((gnus-agent-article-alist gnus-agent-article-alist)
  2075. (gnus-agent-file-loading-cache gnus-agent-file-loading-cache)
  2076. (alist (gnus-agent-load-alist group)))
  2077. (when alist
  2078. (setq minmax
  2079. (cons (caar alist)
  2080. (caar (last alist))))
  2081. (gnus-agent-set-local group (car minmax) (cdr minmax)
  2082. gmane gnus-command-method local))))
  2083. minmax))
  2084. (defun gnus-agent-set-local (group min max &optional gmane method local)
  2085. (let* ((gmane (or gmane (gnus-group-real-name group)))
  2086. (gnus-command-method (or method (gnus-find-method-for-group group)))
  2087. (local (or local (gnus-agent-load-local)))
  2088. (symb (intern gmane local))
  2089. (minmax (and (boundp symb) (symbol-value symb))))
  2090. (if (cond ((and minmax
  2091. (or (not (eq min (car minmax)))
  2092. (not (eq max (cdr minmax))))
  2093. min
  2094. max)
  2095. (setcar minmax min)
  2096. (setcdr minmax max)
  2097. t)
  2098. (minmax
  2099. nil)
  2100. ((and min max)
  2101. (set symb (cons min max))
  2102. t)
  2103. (t
  2104. (unintern symb local)))
  2105. (set (intern "+dirty" local) t))))
  2106. (defun gnus-agent-article-name (article group)
  2107. (expand-file-name article
  2108. (file-name-as-directory
  2109. (gnus-agent-group-pathname group))))
  2110. (defun gnus-agent-batch-confirmation (msg)
  2111. "Show error message and return t."
  2112. (gnus-message 1 "%s" msg)
  2113. t)
  2114. ;;;###autoload
  2115. (defun gnus-agent-batch-fetch ()
  2116. "Start Gnus and fetch session."
  2117. (interactive)
  2118. (gnus)
  2119. (let ((gnus-agent-confirmation-function 'gnus-agent-batch-confirmation))
  2120. (gnus-agent-fetch-session))
  2121. (gnus-group-exit))
  2122. (defun gnus-agent-fetch-session ()
  2123. "Fetch all articles and headers that are eligible for fetching."
  2124. (interactive)
  2125. (unless gnus-agent-covered-methods
  2126. (error "No servers are covered by the Gnus agent"))
  2127. (unless gnus-plugged
  2128. (error "Can't fetch articles while Gnus is unplugged"))
  2129. (let ((methods (gnus-agent-covered-methods))
  2130. groups group gnus-command-method)
  2131. (save-excursion
  2132. (while methods
  2133. (setq gnus-command-method (car methods))
  2134. (when (and (or (gnus-server-opened gnus-command-method)
  2135. (gnus-open-server gnus-command-method))
  2136. (gnus-online gnus-command-method))
  2137. (setq groups (gnus-groups-from-server (car methods)))
  2138. (gnus-agent-with-fetch
  2139. (while (setq group (pop groups))
  2140. (when (<= (gnus-group-level group)
  2141. gnus-agent-handle-level)
  2142. (if (or debug-on-error debug-on-quit)
  2143. (gnus-agent-fetch-group-1
  2144. group gnus-command-method)
  2145. (condition-case err
  2146. (gnus-agent-fetch-group-1
  2147. group gnus-command-method)
  2148. (error
  2149. (unless (funcall gnus-agent-confirmation-function
  2150. (format "Error %s while fetching session. Should gnus continue? "
  2151. (error-message-string err)))
  2152. (error "Cannot fetch articles into the Gnus agent")))
  2153. (quit
  2154. (gnus-agent-regenerate-group group)
  2155. (unless (funcall gnus-agent-confirmation-function
  2156. (format
  2157. "%s while fetching session. Should gnus continue? "
  2158. (error-message-string err)))
  2159. (signal 'quit
  2160. "Cannot fetch articles into the Gnus agent")))))))))
  2161. (setq methods (cdr methods)))
  2162. (gnus-run-hooks 'gnus-agent-fetched-hook)
  2163. (gnus-message 6 "Finished fetching articles into the Gnus agent"))))
  2164. (defvar gnus-agent-short-article 500
  2165. "Articles that have fewer lines than this are short.")
  2166. (defvar gnus-agent-long-article 1000
  2167. "Articles that have more lines than this are long.")
  2168. (defvar gnus-agent-low-score 0
  2169. "Articles that have a score lower than this have a low score.")
  2170. (defvar gnus-agent-high-score 0
  2171. "Articles that have a score higher than this have a high score.")
  2172. (defun gnus-agent-fetch-group-1 (group method)
  2173. "Fetch GROUP."
  2174. (let ((gnus-command-method method)
  2175. (gnus-newsgroup-name group)
  2176. (gnus-newsgroup-dependencies gnus-newsgroup-dependencies)
  2177. (gnus-newsgroup-headers gnus-newsgroup-headers)
  2178. (gnus-newsgroup-scored gnus-newsgroup-scored)
  2179. (gnus-use-cache gnus-use-cache)
  2180. (gnus-summary-expunge-below gnus-summary-expunge-below)
  2181. (gnus-summary-mark-below gnus-summary-mark-below)
  2182. (gnus-orphan-score gnus-orphan-score)
  2183. ;; Maybe some other gnus-summary local variables should also
  2184. ;; be put here.
  2185. gnus-headers
  2186. gnus-score
  2187. articles
  2188. predicate info marks
  2189. )
  2190. (unless (gnus-check-group group)
  2191. (error "Can't open server for %s" group))
  2192. ;; Fetch headers.
  2193. (when (or gnus-newsgroup-active
  2194. (gnus-active group)
  2195. (gnus-activate-group group))
  2196. (let ((marked-articles gnus-newsgroup-downloadable))
  2197. ;; Identify the articles marked for download
  2198. (unless gnus-newsgroup-active
  2199. ;; The variable gnus-newsgroup-active was selected as I need
  2200. ;; a gnus-summary local variable that is NOT bound to any
  2201. ;; value (its global value should default to nil).
  2202. (dolist (mark gnus-agent-download-marks)
  2203. (let ((arts (cdr (assq mark (gnus-info-marks
  2204. (setq info (gnus-get-info group)))))))
  2205. (when arts
  2206. (setq marked-articles (nconc (gnus-uncompress-range arts)
  2207. marked-articles))
  2208. ))))
  2209. (setq marked-articles (sort marked-articles '<))
  2210. ;; Fetch any new articles from the server
  2211. (setq articles (gnus-agent-fetch-headers group))
  2212. ;; Merge new articles with marked
  2213. (setq articles (sort (append marked-articles articles) '<))
  2214. (when articles
  2215. ;; Parse them and see which articles we want to fetch.
  2216. (setq gnus-newsgroup-dependencies
  2217. (or gnus-newsgroup-dependencies
  2218. (make-vector (length articles) 0)))
  2219. (setq gnus-newsgroup-headers
  2220. (or gnus-newsgroup-headers
  2221. (gnus-get-newsgroup-headers-xover articles nil nil
  2222. group)))
  2223. ;; `gnus-agent-overview-buffer' may be killed for
  2224. ;; timeout reason. If so, recreate it.
  2225. (gnus-agent-create-buffer)
  2226. (setq predicate
  2227. (gnus-get-predicate
  2228. (gnus-agent-find-parameter group 'agent-predicate)))
  2229. ;; If the selection predicate requires scoring, score each header
  2230. (unless (memq predicate '(gnus-agent-true gnus-agent-false))
  2231. (let ((score-param
  2232. (gnus-agent-find-parameter group 'agent-score-file)))
  2233. ;; Translate score-param into real one
  2234. (cond
  2235. ((not score-param))
  2236. ((eq score-param 'file)
  2237. (setq score-param (gnus-all-score-files group)))
  2238. ((stringp (car score-param)))
  2239. (t
  2240. (setq score-param (list (list score-param)))))
  2241. (when score-param
  2242. (gnus-score-headers score-param))))
  2243. (unless (and (eq predicate 'gnus-agent-false)
  2244. (not marked-articles))
  2245. (let ((arts (list nil)))
  2246. (let ((arts-tail arts)
  2247. (alist (gnus-agent-load-alist group))
  2248. (marked-articles marked-articles)
  2249. (gnus-newsgroup-headers gnus-newsgroup-headers))
  2250. (while (setq gnus-headers (pop gnus-newsgroup-headers))
  2251. (let ((num (mail-header-number gnus-headers)))
  2252. ;; Determine if this article is already in the cache
  2253. (while (and alist
  2254. (> num (caar alist)))
  2255. (setq alist (cdr alist)))
  2256. (unless (and (eq num (caar alist))
  2257. (cdar alist))
  2258. ;; Determine if this article was marked for download.
  2259. (while (and marked-articles
  2260. (> num (car marked-articles)))
  2261. (setq marked-articles
  2262. (cdr marked-articles)))
  2263. ;; When this article is marked, or selected by the
  2264. ;; predicate, add it to the download list
  2265. (when (or (eq num (car marked-articles))
  2266. (let ((gnus-score
  2267. (or (cdr
  2268. (assq num gnus-newsgroup-scored))
  2269. gnus-summary-default-score))
  2270. (gnus-agent-long-article
  2271. (gnus-agent-find-parameter
  2272. group 'agent-long-article))
  2273. (gnus-agent-short-article
  2274. (gnus-agent-find-parameter
  2275. group 'agent-short-article))
  2276. (gnus-agent-low-score
  2277. (gnus-agent-find-parameter
  2278. group 'agent-low-score))
  2279. (gnus-agent-high-score
  2280. (gnus-agent-find-parameter
  2281. group 'agent-high-score))
  2282. (gnus-agent-expire-days
  2283. (gnus-agent-find-parameter
  2284. group 'agent-days-until-old)))
  2285. (funcall predicate)))
  2286. (gnus-agent-append-to-list arts-tail num))))))
  2287. (let (fetched-articles)
  2288. ;; Fetch all selected articles
  2289. (setq gnus-newsgroup-undownloaded
  2290. (gnus-sorted-ndifference
  2291. gnus-newsgroup-undownloaded
  2292. (setq fetched-articles
  2293. (if (cdr arts)
  2294. (gnus-agent-fetch-articles group (cdr arts))
  2295. nil))))
  2296. (let ((unfetched-articles
  2297. (gnus-sorted-ndifference (cdr arts) fetched-articles)))
  2298. (if gnus-newsgroup-active
  2299. ;; Update the summary buffer
  2300. (progn
  2301. (dolist (article marked-articles)
  2302. (gnus-summary-set-agent-mark article t))
  2303. (dolist (article fetched-articles)
  2304. (when gnus-agent-mark-unread-after-downloaded
  2305. (setq gnus-newsgroup-downloadable
  2306. (delq article gnus-newsgroup-downloadable))
  2307. (gnus-summary-mark-article
  2308. article gnus-unread-mark))
  2309. (when (gnus-summary-goto-subject article nil t)
  2310. (gnus-summary-update-download-mark article)))
  2311. (dolist (article unfetched-articles)
  2312. (gnus-summary-mark-article
  2313. article gnus-canceled-mark)))
  2314. ;; Update the group buffer.
  2315. ;; When some, or all, of the marked articles came
  2316. ;; from the download mark. Remove that mark. I
  2317. ;; didn't do this earlier as I only want to remove
  2318. ;; the marks after the fetch is completed.
  2319. (dolist (mark gnus-agent-download-marks)
  2320. (when (eq mark 'download)
  2321. (let ((marked-arts
  2322. (assq mark (gnus-info-marks
  2323. (setq info (gnus-get-info group))))))
  2324. (when (cdr marked-arts)
  2325. (setq marks
  2326. (delq marked-arts (gnus-info-marks info)))
  2327. (gnus-info-set-marks info marks)))))
  2328. (let ((read (gnus-info-read
  2329. (or info (setq info (gnus-get-info group))))))
  2330. (gnus-info-set-read
  2331. info (gnus-add-to-range read unfetched-articles)))
  2332. (gnus-group-update-group group t)
  2333. (sit-for 0)
  2334. (gnus-dribble-enter
  2335. (concat "(gnus-group-set-info '"
  2336. (gnus-prin1-to-string info)
  2337. ")")
  2338. (concat "^(gnus-group-set-info '(\""
  2339. (regexp-quote group) "\""))))))))))))
  2340. ;;;
  2341. ;;; Agent Category Mode
  2342. ;;;
  2343. (defvar gnus-category-mode-hook nil
  2344. "Hook run in `gnus-category-mode' buffers.")
  2345. (defvar gnus-category-line-format " %(%20c%): %g\n"
  2346. "Format of category lines.
  2347. Valid specifiers include:
  2348. %c Topic name (string)
  2349. %g The number of groups in the topic (integer)
  2350. General format specifiers can also be used. See Info node
  2351. `(gnus)Formatting Variables'.")
  2352. (defvar gnus-category-mode-line-format "Gnus: %%b"
  2353. "The format specification for the category mode line.")
  2354. (defvar gnus-agent-predicate 'false
  2355. "The selection predicate used when no other source is available.")
  2356. ;;; Internal variables.
  2357. (defvar gnus-category-buffer "*Agent Category*")
  2358. (defvar gnus-tmp-name)
  2359. (defvar gnus-tmp-groups)
  2360. (defvar gnus-category-line-format-alist
  2361. `((?c gnus-tmp-name ?s)
  2362. (?g gnus-tmp-groups ?d)))
  2363. (defvar gnus-category-mode-line-format-alist
  2364. `((?u user-defined ?s)))
  2365. (defvar gnus-category-line-format-spec nil)
  2366. (defvar gnus-category-mode-line-format-spec nil)
  2367. (defvar gnus-category-mode-map nil)
  2368. (put 'gnus-category-mode 'mode-class 'special)
  2369. (unless gnus-category-mode-map
  2370. (setq gnus-category-mode-map (make-sparse-keymap))
  2371. (suppress-keymap gnus-category-mode-map)
  2372. (gnus-define-keys gnus-category-mode-map
  2373. "q" gnus-category-exit
  2374. "k" gnus-category-kill
  2375. "c" gnus-category-copy
  2376. "a" gnus-category-add
  2377. "e" gnus-agent-customize-category
  2378. "p" gnus-category-edit-predicate
  2379. "g" gnus-category-edit-groups
  2380. "s" gnus-category-edit-score
  2381. "l" gnus-category-list
  2382. "\C-c\C-i" gnus-info-find-node
  2383. "\C-c\C-b" gnus-bug))
  2384. (defvar gnus-category-menu-hook nil
  2385. "*Hook run after the creation of the menu.")
  2386. (defun gnus-category-make-menu-bar ()
  2387. (gnus-turn-off-edit-menu 'category)
  2388. (unless (boundp 'gnus-category-menu)
  2389. (easy-menu-define
  2390. gnus-category-menu gnus-category-mode-map ""
  2391. '("Categories"
  2392. ["Add" gnus-category-add t]
  2393. ["Kill" gnus-category-kill t]
  2394. ["Copy" gnus-category-copy t]
  2395. ["Edit category" gnus-agent-customize-category t]
  2396. ["Edit predicate" gnus-category-edit-predicate t]
  2397. ["Edit score" gnus-category-edit-score t]
  2398. ["Edit groups" gnus-category-edit-groups t]
  2399. ["Exit" gnus-category-exit t]))
  2400. (gnus-run-hooks 'gnus-category-menu-hook)))
  2401. (define-derived-mode gnus-category-mode fundamental-mode "Category"
  2402. "Major mode for listing and editing agent categories.
  2403. All normal editing commands are switched off.
  2404. \\<gnus-category-mode-map>
  2405. For more in-depth information on this mode, read the manual
  2406. \(`\\[gnus-info-find-node]').
  2407. The following commands are available:
  2408. \\{gnus-category-mode-map}"
  2409. (when (gnus-visual-p 'category-menu 'menu)
  2410. (gnus-category-make-menu-bar))
  2411. (gnus-simplify-mode-line)
  2412. (gnus-set-default-directory)
  2413. (setq mode-line-process nil)
  2414. (buffer-disable-undo)
  2415. (setq truncate-lines t)
  2416. (setq buffer-read-only t))
  2417. (defalias 'gnus-category-position-point 'gnus-goto-colon)
  2418. (defun gnus-category-insert-line (category)
  2419. (let* ((gnus-tmp-name (format "%s" (car category)))
  2420. (gnus-tmp-groups (length (gnus-agent-cat-groups category))))
  2421. (beginning-of-line)
  2422. (gnus-add-text-properties
  2423. (point)
  2424. (prog1 (1+ (point))
  2425. ;; Insert the text.
  2426. (eval gnus-category-line-format-spec))
  2427. (list 'gnus-category gnus-tmp-name))))
  2428. (defun gnus-enter-category-buffer ()
  2429. "Go to the Category buffer."
  2430. (interactive)
  2431. (gnus-category-setup-buffer)
  2432. (gnus-configure-windows 'category)
  2433. (gnus-category-prepare))
  2434. (defun gnus-category-setup-buffer ()
  2435. (unless (get-buffer gnus-category-buffer)
  2436. (with-current-buffer (gnus-get-buffer-create gnus-category-buffer)
  2437. (gnus-category-mode))))
  2438. (defun gnus-category-prepare ()
  2439. (gnus-set-format 'category-mode)
  2440. (gnus-set-format 'category t)
  2441. (let ((alist gnus-category-alist)
  2442. (buffer-read-only nil))
  2443. (erase-buffer)
  2444. (while alist
  2445. (gnus-category-insert-line (pop alist)))
  2446. (goto-char (point-min))
  2447. (gnus-category-position-point)))
  2448. (defun gnus-category-name ()
  2449. (or (intern (get-text-property (point-at-bol) 'gnus-category))
  2450. (error "No category on the current line")))
  2451. (defun gnus-category-read ()
  2452. "Read the category alist."
  2453. (setq gnus-category-alist
  2454. (or
  2455. (with-temp-buffer
  2456. (ignore-errors
  2457. (nnheader-insert-file-contents (nnheader-concat gnus-agent-directory "lib/categories"))
  2458. (goto-char (point-min))
  2459. ;; This code isn't temp, it will be needed so long as
  2460. ;; anyone may be migrating from an older version.
  2461. ;; Once we're certain that people will not revert to an
  2462. ;; earlier version, we can take out the old-list code in
  2463. ;; gnus-category-write.
  2464. (let* ((old-list (read (current-buffer)))
  2465. (new-list (ignore-errors (read (current-buffer)))))
  2466. (if new-list
  2467. new-list
  2468. ;; Convert from a positional list to an alist.
  2469. (mapcar
  2470. (lambda (c)
  2471. (setcdr c
  2472. (delq nil
  2473. (gnus-mapcar
  2474. (lambda (valu symb)
  2475. (if valu
  2476. (cons symb valu)))
  2477. (cdr c)
  2478. '(agent-predicate agent-score-file agent-groups))))
  2479. c)
  2480. old-list)))))
  2481. (list (gnus-agent-cat-make 'default 'short)))))
  2482. (defun gnus-category-write ()
  2483. "Write the category alist."
  2484. (setq gnus-category-predicate-cache nil
  2485. gnus-category-group-cache nil)
  2486. (gnus-make-directory (nnheader-concat gnus-agent-directory "lib"))
  2487. (with-temp-file (nnheader-concat gnus-agent-directory "lib/categories")
  2488. ;; This prin1 is temporary. It exists so that people can revert
  2489. ;; to an earlier version of gnus-agent.
  2490. (prin1 (mapcar (lambda (c)
  2491. (list (car c)
  2492. (cdr (assoc 'agent-predicate c))
  2493. (cdr (assoc 'agent-score-file c))
  2494. (cdr (assoc 'agent-groups c))))
  2495. gnus-category-alist)
  2496. (current-buffer))
  2497. (newline)
  2498. (prin1 gnus-category-alist (current-buffer))))
  2499. (defun gnus-category-edit-predicate (category)
  2500. "Edit the predicate for CATEGORY."
  2501. (interactive (list (gnus-category-name)))
  2502. (let ((info (assq category gnus-category-alist)))
  2503. (gnus-edit-form
  2504. (gnus-agent-cat-predicate info)
  2505. (format "Editing the select predicate for category %s" category)
  2506. `(lambda (predicate)
  2507. ;; Avoid run-time execution of setf form
  2508. ;; (setf (gnus-agent-cat-predicate (assq ',category gnus-category-alist))
  2509. ;; predicate)
  2510. ;; use its expansion instead:
  2511. (gnus-agent-cat-set-property (assq ',category gnus-category-alist)
  2512. 'agent-predicate predicate)
  2513. (gnus-category-write)
  2514. (gnus-category-list)))))
  2515. (defun gnus-category-edit-score (category)
  2516. "Edit the score expression for CATEGORY."
  2517. (interactive (list (gnus-category-name)))
  2518. (let ((info (assq category gnus-category-alist)))
  2519. (gnus-edit-form
  2520. (gnus-agent-cat-score-file info)
  2521. (format "Editing the score expression for category %s" category)
  2522. `(lambda (score-file)
  2523. ;; Avoid run-time execution of setf form
  2524. ;; (setf (gnus-agent-cat-score-file (assq ',category gnus-category-alist))
  2525. ;; score-file)
  2526. ;; use its expansion instead:
  2527. (gnus-agent-cat-set-property (assq ',category gnus-category-alist)
  2528. 'agent-score-file score-file)
  2529. (gnus-category-write)
  2530. (gnus-category-list)))))
  2531. (defun gnus-category-edit-groups (category)
  2532. "Edit the group list for CATEGORY."
  2533. (interactive (list (gnus-category-name)))
  2534. (let ((info (assq category gnus-category-alist)))
  2535. (gnus-edit-form
  2536. (gnus-agent-cat-groups info)
  2537. (format "Editing the group list for category %s" category)
  2538. `(lambda (groups)
  2539. ;; Avoid run-time execution of setf form
  2540. ;; (setf (gnus-agent-cat-groups (assq ',category gnus-category-alist))
  2541. ;; groups)
  2542. ;; use its expansion instead:
  2543. (gnus-agent-set-cat-groups (assq ',category gnus-category-alist)
  2544. groups)
  2545. (gnus-category-write)
  2546. (gnus-category-list)))))
  2547. (defun gnus-category-kill (category)
  2548. "Kill the current category."
  2549. (interactive (list (gnus-category-name)))
  2550. (let ((info (assq category gnus-category-alist))
  2551. (buffer-read-only nil))
  2552. (gnus-delete-line)
  2553. (setq gnus-category-alist (delq info gnus-category-alist))
  2554. (gnus-category-write)))
  2555. (defun gnus-category-copy (category to)
  2556. "Copy the current category."
  2557. (interactive (list (gnus-category-name) (intern (read-string "New name: "))))
  2558. (let ((info (assq category gnus-category-alist)))
  2559. (push (let ((newcat (gnus-copy-sequence info)))
  2560. (setf (gnus-agent-cat-name newcat) to)
  2561. (setf (gnus-agent-cat-groups newcat) nil)
  2562. newcat)
  2563. gnus-category-alist)
  2564. (gnus-category-write)
  2565. (gnus-category-list)))
  2566. (defun gnus-category-add (category)
  2567. "Create a new category."
  2568. (interactive "SCategory name: ")
  2569. (when (assq category gnus-category-alist)
  2570. (error "Category %s already exists" category))
  2571. (push (gnus-agent-cat-make category)
  2572. gnus-category-alist)
  2573. (gnus-category-write)
  2574. (gnus-category-list))
  2575. (defun gnus-category-list ()
  2576. "List all categories."
  2577. (interactive)
  2578. (gnus-category-prepare))
  2579. (defun gnus-category-exit ()
  2580. "Return to the group buffer."
  2581. (interactive)
  2582. (kill-buffer (current-buffer))
  2583. (gnus-configure-windows 'group t))
  2584. ;; To avoid having 8-bit characters in the source file.
  2585. (defvar gnus-category-not (list '! 'not (intern (format "%c" 172))))
  2586. (defvar gnus-category-predicate-alist
  2587. '((spam . gnus-agent-spam-p)
  2588. (short . gnus-agent-short-p)
  2589. (long . gnus-agent-long-p)
  2590. (low . gnus-agent-low-scored-p)
  2591. (high . gnus-agent-high-scored-p)
  2592. (read . gnus-agent-read-p)
  2593. (true . gnus-agent-true)
  2594. (false . gnus-agent-false))
  2595. "Mapping from short score predicate symbols to predicate functions.")
  2596. (defun gnus-agent-spam-p ()
  2597. "Say whether an article is spam or not."
  2598. (unless gnus-agent-spam-hashtb
  2599. (setq gnus-agent-spam-hashtb (gnus-make-hashtable 1000)))
  2600. (if (not (equal (mail-header-references gnus-headers) ""))
  2601. nil
  2602. (let ((string (gnus-simplify-subject (mail-header-subject gnus-headers))))
  2603. (prog1
  2604. (gnus-gethash string gnus-agent-spam-hashtb)
  2605. (gnus-sethash string t gnus-agent-spam-hashtb)))))
  2606. (defun gnus-agent-short-p ()
  2607. "Say whether an article is short or not."
  2608. (< (mail-header-lines gnus-headers) gnus-agent-short-article))
  2609. (defun gnus-agent-long-p ()
  2610. "Say whether an article is long or not."
  2611. (> (mail-header-lines gnus-headers) gnus-agent-long-article))
  2612. (defun gnus-agent-low-scored-p ()
  2613. "Say whether an article has a low score or not."
  2614. (< gnus-score gnus-agent-low-score))
  2615. (defun gnus-agent-high-scored-p ()
  2616. "Say whether an article has a high score or not."
  2617. (> gnus-score gnus-agent-high-score))
  2618. (defun gnus-agent-read-p ()
  2619. "Say whether an article is read or not."
  2620. (gnus-member-of-range (mail-header-number gnus-headers)
  2621. (gnus-info-read (gnus-get-info gnus-newsgroup-name))))
  2622. (defun gnus-category-make-function (predicate)
  2623. "Make a function from PREDICATE."
  2624. (let ((func (gnus-category-make-function-1 predicate)))
  2625. (if (and (= (length func) 1)
  2626. (symbolp (car func)))
  2627. (car func)
  2628. (gnus-byte-compile `(lambda () ,func)))))
  2629. (defun gnus-agent-true ()
  2630. "Return t."
  2631. t)
  2632. (defun gnus-agent-false ()
  2633. "Return nil."
  2634. nil)
  2635. (defun gnus-category-make-function-1 (predicate)
  2636. "Make a function from PREDICATE."
  2637. (cond
  2638. ;; Functions are just returned as is.
  2639. ((or (symbolp predicate)
  2640. (functionp predicate))
  2641. `(,(or (cdr (assq predicate gnus-category-predicate-alist))
  2642. predicate)))
  2643. ;; More complex predicate.
  2644. ((consp predicate)
  2645. `(,(cond
  2646. ((memq (car predicate) '(& and))
  2647. 'and)
  2648. ((memq (car predicate) '(| or))
  2649. 'or)
  2650. ((memq (car predicate) gnus-category-not)
  2651. 'not))
  2652. ,@(mapcar 'gnus-category-make-function-1 (cdr predicate))))
  2653. (t
  2654. (error "Unknown predicate type: %s" predicate))))
  2655. (defun gnus-get-predicate (predicate)
  2656. "Return the function implementing PREDICATE."
  2657. (or (cdr (assoc predicate gnus-category-predicate-cache))
  2658. (let ((func (gnus-category-make-function predicate)))
  2659. (push (cons predicate func) gnus-category-predicate-cache)
  2660. func)))
  2661. (defun gnus-predicate-implies-unread (predicate)
  2662. "Say whether PREDICATE implies unread articles only.
  2663. It is okay to miss some cases, but there must be no false positives.
  2664. That is, if this predicate returns true, then indeed the predicate must
  2665. return only unread articles."
  2666. (eq t (gnus-function-implies-unread-1
  2667. (gnus-category-make-function-1 predicate))))
  2668. (defun gnus-function-implies-unread-1 (function)
  2669. "Recursively evaluate a predicate function to determine whether it can select
  2670. any read articles. Returns t if the function is known to never
  2671. return read articles, nil when it is known to always return read
  2672. articles, and t_nil when the function may return both read and unread
  2673. articles."
  2674. (let ((func (car function))
  2675. (args (mapcar 'gnus-function-implies-unread-1 (cdr function))))
  2676. (cond ((eq func 'and)
  2677. (cond ((memq t args) ; if any argument returns only unread articles
  2678. ;; then that argument constrains the result to only unread articles.
  2679. t)
  2680. ((memq 't_nil args) ; if any argument is indeterminate
  2681. ;; then the result is indeterminate
  2682. 't_nil)))
  2683. ((eq func 'or)
  2684. (cond ((memq nil args) ; if any argument returns read articles
  2685. ;; then that argument ensures that the results includes read articles.
  2686. nil)
  2687. ((memq 't_nil args) ; if any argument is indeterminate
  2688. ;; then that argument ensures that the results are indeterminate
  2689. 't_nil)
  2690. (t ; if all arguments return only unread articles
  2691. ;; then the result returns only unread articles
  2692. t)))
  2693. ((eq func 'not)
  2694. (cond ((eq (car args) 't_nil) ; if the argument is indeterminate
  2695. ; then the result is indeterminate
  2696. (car args))
  2697. (t ; otherwise
  2698. ; toggle the result to be the opposite of the argument
  2699. (not (car args)))))
  2700. ((eq func 'gnus-agent-read-p)
  2701. nil) ; The read predicate NEVER returns unread articles
  2702. ((eq func 'gnus-agent-false)
  2703. t) ; The false predicate returns t as the empty set excludes all read articles
  2704. ((eq func 'gnus-agent-true)
  2705. nil) ; The true predicate ALWAYS returns read articles
  2706. ((catch 'found-match
  2707. (let ((alist gnus-category-predicate-alist))
  2708. (while alist
  2709. (if (eq func (cdar alist))
  2710. (throw 'found-match t)
  2711. (setq alist (cdr alist))))))
  2712. 't_nil) ; All other predicates return read and unread articles
  2713. (t
  2714. (error "Unknown predicate function: %s" function)))))
  2715. (defun gnus-group-category (group)
  2716. "Return the category GROUP belongs to."
  2717. (unless gnus-category-group-cache
  2718. (setq gnus-category-group-cache (gnus-make-hashtable 1000))
  2719. (let ((cs gnus-category-alist)
  2720. groups cat)
  2721. (while (setq cat (pop cs))
  2722. (setq groups (gnus-agent-cat-groups cat))
  2723. (while groups
  2724. (gnus-sethash (pop groups) cat gnus-category-group-cache)))))
  2725. (or (gnus-gethash group gnus-category-group-cache)
  2726. (assq 'default gnus-category-alist)))
  2727. (defvar gnus-agent-expire-current-dirs)
  2728. (defvar gnus-agent-expire-stats)
  2729. (defun gnus-agent-expire-group (group &optional articles force)
  2730. "Expire all old articles in GROUP.
  2731. If you want to force expiring of certain articles, this function can
  2732. take ARTICLES, and FORCE parameters as well.
  2733. The articles on which the expiration process runs are selected as follows:
  2734. if ARTICLES is null, all read and unmarked articles.
  2735. if ARTICLES is t, all articles.
  2736. if ARTICLES is a list, just those articles.
  2737. FORCE is equivalent to setting the expiration predicates to true."
  2738. (interactive (list (gnus-agent-read-group)))
  2739. (if (not group)
  2740. (gnus-agent-expire articles group force)
  2741. (let (;; Bind gnus-agent-expire-stats to enable tracking of
  2742. ;; expiration statistics of this single group
  2743. (gnus-agent-expire-stats (list 0 0 0.0)))
  2744. (if (or (not (eq articles t))
  2745. (yes-or-no-p
  2746. (concat "Are you sure that you want to "
  2747. "expire all articles in " group "? ")))
  2748. (let ((gnus-command-method (gnus-find-method-for-group group))
  2749. (overview (gnus-get-buffer-create " *expire overview*"))
  2750. orig)
  2751. (unwind-protect
  2752. (let ((active-file (gnus-agent-lib-file "active")))
  2753. (when (file-exists-p active-file)
  2754. (with-temp-buffer
  2755. (nnheader-insert-file-contents active-file)
  2756. (gnus-active-to-gnus-format
  2757. gnus-command-method
  2758. (setq orig (gnus-make-hashtable
  2759. (count-lines (point-min) (point-max))))))
  2760. (save-excursion
  2761. (gnus-agent-expire-group-1
  2762. group overview (gnus-gethash-safe group orig)
  2763. articles force))))
  2764. (kill-buffer overview))))
  2765. (gnus-message 4 "%s" (gnus-agent-expire-done-message)))))
  2766. (defun gnus-agent-expire-group-1 (group overview active articles force)
  2767. ;; Internal function - requires caller to have set
  2768. ;; gnus-command-method, initialized overview buffer, and to have
  2769. ;; provided a non-nil active
  2770. (let ((dir (gnus-agent-group-pathname group))
  2771. (file-name-coding-system nnmail-pathname-coding-system)
  2772. (decoded (gnus-agent-decoded-group-name group)))
  2773. (gnus-agent-with-refreshed-group
  2774. group
  2775. (when (boundp 'gnus-agent-expire-current-dirs)
  2776. (push dir gnus-agent-expire-current-dirs))
  2777. (if (and (not force)
  2778. (eq 'DISABLE (gnus-agent-find-parameter group
  2779. 'agent-enable-expiration)))
  2780. (gnus-message 5 "Expiry skipping over %s" decoded)
  2781. (gnus-message 5 "Expiring articles in %s" decoded)
  2782. (gnus-agent-load-alist group)
  2783. (let* ((bytes-freed 0)
  2784. (size-files-deleted 0.0)
  2785. (files-deleted 0)
  2786. (nov-entries-deleted 0)
  2787. (info (gnus-get-info group))
  2788. (alist gnus-agent-article-alist)
  2789. (day (- (time-to-days (current-time))
  2790. (gnus-agent-find-parameter group 'agent-days-until-old)))
  2791. (specials (if (and alist
  2792. (not force))
  2793. ;; This could be a bit of a problem. I need to
  2794. ;; keep the last article to avoid refetching
  2795. ;; headers when using nntp in the backend. At
  2796. ;; the same time, if someone uses a backend
  2797. ;; that supports article moving then I may have
  2798. ;; to remove the last article to complete the
  2799. ;; move. Right now, I'm going to assume that
  2800. ;; FORCE overrides specials.
  2801. (list (caar (last alist)))))
  2802. (unreads ;; Articles that are excluded from the
  2803. ;; expiration process
  2804. (cond (gnus-agent-expire-all
  2805. ;; All articles are marked read by global decree
  2806. nil)
  2807. ((eq articles t)
  2808. ;; All articles are marked read by function
  2809. ;; parameter
  2810. nil)
  2811. ((not articles)
  2812. ;; Unread articles are marked protected from
  2813. ;; expiration Don't call
  2814. ;; gnus-list-of-unread-articles as it returns
  2815. ;; articles that have not been fetched into the
  2816. ;; agent.
  2817. (ignore-errors
  2818. (gnus-agent-unread-articles group)))
  2819. (t
  2820. ;; All articles EXCEPT those named by the caller
  2821. ;; are protected from expiration
  2822. (gnus-sorted-difference
  2823. (gnus-uncompress-range
  2824. (cons (caar alist)
  2825. (caar (last alist))))
  2826. (sort articles '<)))))
  2827. (marked ;; More articles that are excluded from the
  2828. ;; expiration process
  2829. (cond (gnus-agent-expire-all
  2830. ;; All articles are unmarked by global decree
  2831. nil)
  2832. ((eq articles t)
  2833. ;; All articles are unmarked by function
  2834. ;; parameter
  2835. nil)
  2836. (articles
  2837. ;; All articles may as well be unmarked as the
  2838. ;; unreads list already names the articles we are
  2839. ;; going to keep
  2840. nil)
  2841. (t
  2842. ;; Ticked and/or dormant articles are excluded
  2843. ;; from expiration
  2844. (nconc
  2845. (gnus-uncompress-range
  2846. (cdr (assq 'tick (gnus-info-marks info))))
  2847. (gnus-uncompress-range
  2848. (cdr (assq 'dormant
  2849. (gnus-info-marks info))))))))
  2850. (nov-file (concat dir ".overview"))
  2851. (cnt 0)
  2852. (completed -1)
  2853. dlist
  2854. type)
  2855. ;; The normal article alist contains elements that look like
  2856. ;; (article# . fetch_date) I need to combine other
  2857. ;; information with this list. For example, a flag indicating
  2858. ;; that a particular article MUST BE KEPT. To do this, I'm
  2859. ;; going to transform the elements to look like (article#
  2860. ;; fetch_date keep_flag NOV_entry_position) Later, I'll reverse
  2861. ;; the process to generate the expired article alist.
  2862. ;; Convert the alist elements to (article# fetch_date nil
  2863. ;; nil).
  2864. (setq dlist (mapcar (lambda (e)
  2865. (list (car e) (cdr e) nil nil)) alist))
  2866. ;; Convert the keep lists to elements that look like (article#
  2867. ;; nil keep_flag nil) then append it to the expanded dlist
  2868. ;; These statements are sorted by ascending precedence of the
  2869. ;; keep_flag.
  2870. (setq dlist (nconc dlist
  2871. (mapcar (lambda (e)
  2872. (list e nil 'unread nil))
  2873. unreads)))
  2874. (setq dlist (nconc dlist
  2875. (mapcar (lambda (e)
  2876. (list e nil 'marked nil))
  2877. marked)))
  2878. (setq dlist (nconc dlist
  2879. (mapcar (lambda (e)
  2880. (list e nil 'special nil))
  2881. specials)))
  2882. (set-buffer overview)
  2883. (erase-buffer)
  2884. (buffer-disable-undo)
  2885. (when (file-exists-p nov-file)
  2886. (gnus-message 7 "gnus-agent-expire: Loading overview...")
  2887. (nnheader-insert-file-contents nov-file)
  2888. (goto-char (point-min))
  2889. (let (p)
  2890. (while (< (setq p (point)) (point-max))
  2891. (condition-case nil
  2892. ;; If I successfully read an integer (the plus zero
  2893. ;; ensures a numeric type), append the position
  2894. ;; to the list
  2895. (push (list (+ 0 (read (current-buffer))) nil nil
  2896. p)
  2897. dlist)
  2898. (error
  2899. (gnus-message 1 "gnus-agent-expire: read error \
  2900. occurred when reading expression at %s in %s. Skipping to next \
  2901. line." (point) nov-file)))
  2902. ;; Whether I succeeded, or failed, it doesn't matter.
  2903. ;; Move to the next line then try again.
  2904. (forward-line 1)))
  2905. (gnus-message
  2906. 7 "gnus-agent-expire: Loading overview... Done"))
  2907. (set-buffer-modified-p nil)
  2908. ;; At this point, all of the information is in dlist. The
  2909. ;; only problem is that much of it is spread across multiple
  2910. ;; entries. Sort then MERGE!!
  2911. (gnus-message 7 "gnus-agent-expire: Sorting entries... ")
  2912. (setq dlist
  2913. (sort dlist
  2914. (lambda (a b)
  2915. (cond ((< (nth 0 a) (nth 0 b))
  2916. t)
  2917. ((> (nth 0 a) (nth 0 b))
  2918. nil)
  2919. (t
  2920. ;; If two entries have the same article-number
  2921. ;; then sort by ascending keep_flag.
  2922. (let* ((kf-score '((special . 0)
  2923. (marked . 1)
  2924. (unread . 2)))
  2925. (a (or (cdr (assq (nth 2 a) kf-score))
  2926. 3))
  2927. (b (or (cdr (assq (nth 2 b) kf-score))
  2928. 3)))
  2929. (<= a b)))))))
  2930. (gnus-message 7 "gnus-agent-expire: Sorting entries... Done")
  2931. (gnus-message 7 "gnus-agent-expire: Merging entries... ")
  2932. (let ((dlist dlist))
  2933. (while (cdr dlist) ; I'm not at the end-of-list
  2934. (if (eq (caar dlist) (caadr dlist))
  2935. (let ((first (cdr (car dlist)))
  2936. (secnd (cdr (cadr dlist))))
  2937. (setcar first (or (car first)
  2938. (car secnd))) ; fetch_date
  2939. (setq first (cdr first)
  2940. secnd (cdr secnd))
  2941. (setcar first (or (car first)
  2942. (car secnd))) ; Keep_flag
  2943. (setq first (cdr first)
  2944. secnd (cdr secnd))
  2945. (setcar first (or (car first)
  2946. (car secnd))) ; NOV_entry_position
  2947. (setcdr dlist (cddr dlist)))
  2948. (setq dlist (cdr dlist)))))
  2949. ;; Check the order of the entry positions. They should be in
  2950. ;; ascending order. If they aren't, the positions must be
  2951. ;; converted to markers.
  2952. (when (catch 'sort-results
  2953. (let ((dlist dlist)
  2954. (prev-pos -1)
  2955. pos)
  2956. (while dlist
  2957. (if (setq pos (nth 3 (pop dlist)))
  2958. (if (< pos prev-pos)
  2959. (throw 'sort-results 'unsorted)
  2960. (setq prev-pos pos))))))
  2961. (gnus-message 7 "gnus-agent-expire: Unsorted overview; inserting markers to compensate.")
  2962. (mapc (lambda (entry)
  2963. (let ((pos (nth 3 entry)))
  2964. (if pos
  2965. (setf (nth 3 entry)
  2966. (set-marker (make-marker)
  2967. pos)))))
  2968. dlist))
  2969. (gnus-message 7 "gnus-agent-expire: Merging entries... Done")
  2970. (let* ((len (float (length dlist)))
  2971. (alist (list nil))
  2972. (tail-alist alist)
  2973. (position-offset 0)
  2974. )
  2975. (while dlist
  2976. (let ((new-completed (truncate (* 100.0
  2977. (/ (setq cnt (1+ cnt))
  2978. len))))
  2979. message-log-max)
  2980. (when (> new-completed completed)
  2981. (setq completed new-completed)
  2982. (gnus-message 7 "%3d%% completed..." completed)))
  2983. (let* ((entry (car dlist))
  2984. (article-number (nth 0 entry))
  2985. (fetch-date (nth 1 entry))
  2986. (keep (nth 2 entry))
  2987. (marker (nth 3 entry)))
  2988. (cond
  2989. ;; Kept articles are unread, marked, or special.
  2990. (keep
  2991. (gnus-agent-message 10
  2992. "gnus-agent-expire: %s:%d: Kept %s article%s."
  2993. decoded article-number keep (if fetch-date " and file" ""))
  2994. (when fetch-date
  2995. (unless (file-exists-p
  2996. (concat dir (number-to-string
  2997. article-number)))
  2998. (setf (nth 1 entry) nil)
  2999. (gnus-agent-message 3 "gnus-agent-expire cleared \
  3000. download flag on %s:%d as the cached article file is missing."
  3001. decoded (caar dlist)))
  3002. (unless marker
  3003. (gnus-message 1 "gnus-agent-expire detected a \
  3004. missing NOV entry. Run gnus-agent-regenerate-group to restore it.")))
  3005. (gnus-agent-append-to-list
  3006. tail-alist
  3007. (cons article-number fetch-date)))
  3008. ;; The following articles are READ, UNMARKED, and
  3009. ;; ORDINARY. See if they can be EXPIRED!!!
  3010. ((setq type
  3011. (cond
  3012. ((not (integerp fetch-date))
  3013. 'read) ;; never fetched article (may expire
  3014. ;; right now)
  3015. ((not (file-exists-p
  3016. (concat dir (number-to-string
  3017. article-number))))
  3018. (setf (nth 1 entry) nil)
  3019. 'externally-expired) ;; Can't find the cached
  3020. ;; article. Handle case
  3021. ;; as though this article
  3022. ;; was never fetched.
  3023. ;; We now have the arrival day, so we see
  3024. ;; whether it's old enough to be expired.
  3025. ((< fetch-date day)
  3026. 'expired)
  3027. (force
  3028. 'forced)))
  3029. ;; I found some reason to expire this entry.
  3030. (let ((actions nil))
  3031. (when (memq type '(forced expired))
  3032. (ignore-errors ; Just being paranoid.
  3033. (let* ((file-name (nnheader-concat dir (number-to-string
  3034. article-number)))
  3035. (size (float (nth 7 (file-attributes file-name)))))
  3036. (incf bytes-freed size)
  3037. (incf size-files-deleted size)
  3038. (incf files-deleted)
  3039. (delete-file file-name))
  3040. (push "expired cached article" actions))
  3041. (setf (nth 1 entry) nil)
  3042. )
  3043. (when marker
  3044. (push "NOV entry removed" actions)
  3045. (goto-char (if (markerp marker)
  3046. marker
  3047. (- marker position-offset)))
  3048. (incf nov-entries-deleted)
  3049. (let* ((from (point-at-bol))
  3050. (to (progn (forward-line 1) (point)))
  3051. (freed (- to from)))
  3052. (incf bytes-freed freed)
  3053. (incf position-offset freed)
  3054. (delete-region from to)))
  3055. ;; If considering all articles is set, I can only
  3056. ;; expire article IDs that are no longer in the
  3057. ;; active range (That is, articles that precede the
  3058. ;; first article in the new alist).
  3059. (if (and gnus-agent-consider-all-articles
  3060. (>= article-number (car active)))
  3061. ;; I have to keep this ID in the alist
  3062. (gnus-agent-append-to-list
  3063. tail-alist (cons article-number fetch-date))
  3064. (push (format "Removed %s article number from \
  3065. article alist" type) actions))
  3066. (when actions
  3067. (gnus-agent-message 8 "gnus-agent-expire: %s:%d: %s"
  3068. decoded article-number
  3069. (mapconcat 'identity actions ", ")))))
  3070. (t
  3071. (gnus-agent-message
  3072. 10 "gnus-agent-expire: %s:%d: Article kept as \
  3073. expiration tests failed." decoded article-number)
  3074. (gnus-agent-append-to-list
  3075. tail-alist (cons article-number fetch-date)))
  3076. )
  3077. ;; Remove markers as I intend to reuse this buffer again.
  3078. (when (and marker
  3079. (markerp marker))
  3080. (set-marker marker nil))
  3081. (setq dlist (cdr dlist))))
  3082. (setq alist (cdr alist))
  3083. (let ((inhibit-quit t))
  3084. (unless (equal alist gnus-agent-article-alist)
  3085. (setq gnus-agent-article-alist alist)
  3086. (gnus-agent-save-alist group))
  3087. (when (buffer-modified-p)
  3088. (let ((coding-system-for-write
  3089. gnus-agent-file-coding-system))
  3090. (gnus-make-directory dir)
  3091. (write-region (point-min) (point-max) nov-file nil
  3092. 'silent)
  3093. ;; clear the modified flag as that I'm not confused by
  3094. ;; its status on the next pass through this routine.
  3095. (set-buffer-modified-p nil)
  3096. (gnus-agent-update-view-total-fetched-for group t)))
  3097. (when (eq articles t)
  3098. (gnus-summary-update-info))))
  3099. (when (boundp 'gnus-agent-expire-stats)
  3100. (let ((stats gnus-agent-expire-stats))
  3101. (incf (nth 2 stats) bytes-freed)
  3102. (incf (nth 1 stats) files-deleted)
  3103. (incf (nth 0 stats) nov-entries-deleted)))
  3104. (gnus-agent-update-files-total-fetched-for group (- size-files-deleted)))))))
  3105. (defun gnus-agent-expire (&optional articles group force)
  3106. "Expire all old articles.
  3107. If you want to force expiring of certain articles, this function can
  3108. take ARTICLES, GROUP and FORCE parameters as well.
  3109. The articles on which the expiration process runs are selected as follows:
  3110. if ARTICLES is null, all read and unmarked articles.
  3111. if ARTICLES is t, all articles.
  3112. if ARTICLES is a list, just those articles.
  3113. Setting GROUP will limit expiration to that group.
  3114. FORCE is equivalent to setting the expiration predicates to true."
  3115. (interactive)
  3116. (if group
  3117. (gnus-agent-expire-group group articles force)
  3118. (if (or (not (eq articles t))
  3119. (yes-or-no-p "Are you sure that you want to expire all \
  3120. articles in every agentized group? "))
  3121. (let ((methods (gnus-agent-covered-methods))
  3122. ;; Bind gnus-agent-expire-current-dirs to enable tracking
  3123. ;; of agent directories.
  3124. (gnus-agent-expire-current-dirs nil)
  3125. ;; Bind gnus-agent-expire-stats to enable tracking of
  3126. ;; expiration statistics across all groups
  3127. (gnus-agent-expire-stats (list 0 0 0.0))
  3128. gnus-command-method overview orig)
  3129. (setq overview (gnus-get-buffer-create " *expire overview*"))
  3130. (unwind-protect
  3131. (while (setq gnus-command-method (pop methods))
  3132. (let ((active-file (gnus-agent-lib-file "active")))
  3133. (when (file-exists-p active-file)
  3134. (with-temp-buffer
  3135. (nnheader-insert-file-contents active-file)
  3136. (gnus-active-to-gnus-format
  3137. gnus-command-method
  3138. (setq orig (gnus-make-hashtable
  3139. (count-lines (point-min) (point-max))))))
  3140. (dolist (expiring-group (gnus-groups-from-server
  3141. gnus-command-method))
  3142. (let* ((active
  3143. (gnus-gethash-safe expiring-group orig)))
  3144. (when active
  3145. (save-excursion
  3146. (gnus-agent-expire-group-1
  3147. expiring-group overview active articles force))))))))
  3148. (kill-buffer overview))
  3149. (gnus-agent-expire-unagentized-dirs)
  3150. (gnus-message 4 "%s" (gnus-agent-expire-done-message))))))
  3151. (defun gnus-agent-expire-done-message ()
  3152. (if (and (> gnus-verbose 4)
  3153. (boundp 'gnus-agent-expire-stats))
  3154. (let* ((stats gnus-agent-expire-stats)
  3155. (size (nth 2 stats))
  3156. (units '(B KB MB GB)))
  3157. (while (and (> size 1024.0)
  3158. (cdr units))
  3159. (setq size (/ size 1024.0)
  3160. units (cdr units)))
  3161. (format "Expiry recovered %d NOV entries, deleted %d files,\
  3162. and freed %.f %s."
  3163. (nth 0 stats)
  3164. (nth 1 stats)
  3165. size (car units)))
  3166. "Expiry...done"))
  3167. (defun gnus-agent-expire-unagentized-dirs ()
  3168. (when (and gnus-agent-expire-unagentized-dirs
  3169. (boundp 'gnus-agent-expire-current-dirs))
  3170. (let* ((keep (gnus-make-hashtable))
  3171. (file-name-coding-system nnmail-pathname-coding-system))
  3172. (gnus-sethash gnus-agent-directory t keep)
  3173. (dolist (dir gnus-agent-expire-current-dirs)
  3174. (when (and (stringp dir)
  3175. (file-directory-p dir))
  3176. (while (not (gnus-gethash dir keep))
  3177. (gnus-sethash dir t keep)
  3178. (setq dir (file-name-directory (directory-file-name dir))))))
  3179. (let* (to-remove
  3180. checker
  3181. (checker
  3182. (function
  3183. (lambda (d)
  3184. "Given a directory, check it and its subdirectories for
  3185. membership in the keep hash. If it isn't found, add
  3186. it to to-remove."
  3187. (let ((files (directory-files d))
  3188. file)
  3189. (while (setq file (pop files))
  3190. (cond ((equal file ".") ; Ignore self
  3191. nil)
  3192. ((equal file "..") ; Ignore parent
  3193. nil)
  3194. ((equal file ".overview")
  3195. ;; Directory must contain .overview to be
  3196. ;; agent's cache of a group.
  3197. (let ((d (file-name-as-directory d))
  3198. r)
  3199. ;; Search ancestor's for last directory NOT
  3200. ;; found in keep hash.
  3201. (while (not (gnus-gethash
  3202. (setq d (file-name-directory d)) keep))
  3203. (setq r d
  3204. d (directory-file-name d)))
  3205. ;; if ANY ancestor was NOT in keep hash and
  3206. ;; it's not already in to-remove, add it to
  3207. ;; to-remove.
  3208. (if (and r
  3209. (not (member r to-remove)))
  3210. (push r to-remove))))
  3211. ((file-directory-p (setq file (nnheader-concat d file)))
  3212. (funcall checker file)))))))))
  3213. (funcall checker (expand-file-name gnus-agent-directory))
  3214. (when (and to-remove
  3215. (or gnus-expert-user
  3216. (gnus-y-or-n-p
  3217. "gnus-agent-expire has identified local directories that are\
  3218. not currently required by any agentized group. Do you wish to consider\
  3219. deleting them?")))
  3220. (while to-remove
  3221. (let ((dir (pop to-remove)))
  3222. (if (or gnus-expert-user
  3223. (gnus-y-or-n-p (format "Delete %s? " dir)))
  3224. (let* (delete-recursive
  3225. files f
  3226. (delete-recursive
  3227. (function
  3228. (lambda (f-or-d)
  3229. (ignore-errors
  3230. (if (file-directory-p f-or-d)
  3231. (condition-case nil
  3232. (delete-directory f-or-d)
  3233. (file-error
  3234. (setq files (directory-files f-or-d))
  3235. (while files
  3236. (setq f (pop files))
  3237. (or (member f '("." ".."))
  3238. (funcall delete-recursive
  3239. (nnheader-concat
  3240. f-or-d f))))
  3241. (delete-directory f-or-d)))
  3242. (delete-file f-or-d)))))))
  3243. (funcall delete-recursive dir))))))))))
  3244. ;;;###autoload
  3245. (defun gnus-agent-batch ()
  3246. "Start Gnus, send queue and fetch session."
  3247. (interactive)
  3248. (let ((init-file-user "")
  3249. (gnus-always-read-dribble-file t))
  3250. (gnus))
  3251. (let ((gnus-agent-confirmation-function 'gnus-agent-batch-confirmation))
  3252. (gnus-group-send-queue)
  3253. (gnus-agent-fetch-session)))
  3254. (defun gnus-agent-unread-articles (group)
  3255. (let* ((read (gnus-info-read (gnus-get-info group)))
  3256. (known (gnus-agent-load-alist group))
  3257. (unread (list nil))
  3258. (tail-unread unread))
  3259. (while (and known read)
  3260. (let ((candidate (car (pop known))))
  3261. (while (let* ((range (car read))
  3262. (min (if (numberp range) range (car range)))
  3263. (max (if (numberp range) range (cdr range))))
  3264. (cond ((or (not min)
  3265. (< candidate min))
  3266. (gnus-agent-append-to-list tail-unread candidate)
  3267. nil)
  3268. ((> candidate max)
  3269. (setq read (cdr read))
  3270. ;; return t so that I always loop one more
  3271. ;; time. If I just iterated off the end of
  3272. ;; read, min will become nil and the current
  3273. ;; candidate will be added to the unread list.
  3274. t))))))
  3275. (while known
  3276. (gnus-agent-append-to-list tail-unread (car (pop known))))
  3277. (cdr unread)))
  3278. (defun gnus-agent-uncached-articles (articles group &optional cached-header)
  3279. "Restrict ARTICLES to numbers already fetched.
  3280. Returns a sublist of ARTICLES that excludes those article ids in GROUP
  3281. that have already been fetched.
  3282. If CACHED-HEADER is nil, articles are only excluded if the article itself
  3283. has been fetched."
  3284. ;; Logically equivalent to: (gnus-sorted-difference articles (mapcar
  3285. ;; 'car gnus-agent-article-alist))
  3286. ;; Functionally, I don't need to construct a temp list using mapcar.
  3287. (if (and (or gnus-agent-cache (not gnus-plugged))
  3288. (gnus-agent-load-alist group))
  3289. (let* ((ref gnus-agent-article-alist)
  3290. (arts articles)
  3291. (uncached (list nil))
  3292. (tail-uncached uncached))
  3293. (while (and ref arts)
  3294. (let ((v1 (car arts))
  3295. (v2 (caar ref)))
  3296. (cond ((< v1 v2) ; v1 does not appear in the reference list
  3297. (gnus-agent-append-to-list tail-uncached v1)
  3298. (setq arts (cdr arts)))
  3299. ((= v1 v2)
  3300. (unless (or cached-header (cdar ref)) ; v1 is already cached
  3301. (gnus-agent-append-to-list tail-uncached v1))
  3302. (setq arts (cdr arts))
  3303. (setq ref (cdr ref)))
  3304. (t ; reference article (v2) precedes the list being filtered
  3305. (setq ref (cdr ref))))))
  3306. (while arts
  3307. (gnus-agent-append-to-list tail-uncached (pop arts)))
  3308. (cdr uncached))
  3309. ;; if gnus-agent-load-alist fails, no articles are cached.
  3310. articles))
  3311. (defun gnus-agent-retrieve-headers (articles group &optional fetch-old)
  3312. (save-excursion
  3313. (gnus-agent-create-buffer)
  3314. (let ((gnus-decode-encoded-word-function 'identity)
  3315. (gnus-decode-encoded-address-function 'identity)
  3316. (file (gnus-agent-article-name ".overview" group))
  3317. uncached-articles
  3318. (file-name-coding-system nnmail-pathname-coding-system))
  3319. (gnus-make-directory (nnheader-translate-file-chars
  3320. (file-name-directory file) t))
  3321. (when fetch-old
  3322. (setq articles (gnus-uncompress-range
  3323. (cons (if (numberp fetch-old)
  3324. (max 1 (- (car articles) fetch-old))
  3325. 1)
  3326. (car (last articles))))))
  3327. ;; Populate temp buffer with known headers
  3328. (when (file-exists-p file)
  3329. (with-current-buffer gnus-agent-overview-buffer
  3330. (erase-buffer)
  3331. (let ((nnheader-file-coding-system
  3332. gnus-agent-file-coding-system))
  3333. (nnheader-insert-nov-file file (car articles)))))
  3334. (if (setq uncached-articles (gnus-agent-uncached-articles articles group
  3335. t))
  3336. (progn
  3337. ;; Populate nntp-server-buffer with uncached headers
  3338. (set-buffer nntp-server-buffer)
  3339. (erase-buffer)
  3340. (cond ((not (eq 'nov (let (gnus-agent) ; Turn off agent
  3341. (gnus-retrieve-headers
  3342. uncached-articles group))))
  3343. (nnvirtual-convert-headers))
  3344. ((eq 'nntp (car gnus-current-select-method))
  3345. ;; The author of gnus-get-newsgroup-headers-xover
  3346. ;; reports that the XOVER command is commonly
  3347. ;; unreliable. The problem is that recently
  3348. ;; posted articles may not be entered into the
  3349. ;; NOV database in time to respond to my XOVER
  3350. ;; query.
  3351. ;;
  3352. ;; I'm going to use his assumption that the NOV
  3353. ;; database is updated in order of ascending
  3354. ;; article ID. Therefore, a response containing
  3355. ;; article ID N implies that all articles from 1
  3356. ;; to N-1 are up-to-date. Therefore, missing
  3357. ;; articles in that range have expired.
  3358. (set-buffer nntp-server-buffer)
  3359. (let* ((fetched-articles (list nil))
  3360. (tail-fetched-articles fetched-articles)
  3361. (min (car articles))
  3362. (max (car (last articles))))
  3363. ;; Get the list of articles that were fetched
  3364. (goto-char (point-min))
  3365. (let ((pm (point-max))
  3366. art)
  3367. (while (< (point) pm)
  3368. (when (setq art (gnus-agent-read-article-number))
  3369. (gnus-agent-append-to-list tail-fetched-articles art))
  3370. (forward-line 1)))
  3371. ;; Clip this list to the headers that will
  3372. ;; actually be returned
  3373. (setq fetched-articles (gnus-list-range-intersection
  3374. (cdr fetched-articles)
  3375. (cons min max)))
  3376. ;; Clip the uncached articles list to exclude
  3377. ;; IDs after the last FETCHED header. The
  3378. ;; excluded IDs may be fetchable using HEAD.
  3379. (if (car tail-fetched-articles)
  3380. (setq uncached-articles
  3381. (gnus-list-range-intersection
  3382. uncached-articles
  3383. (cons (car uncached-articles)
  3384. (car tail-fetched-articles)))))
  3385. ;; Create the list of articles that were
  3386. ;; "successfully" fetched. Success, in this
  3387. ;; case, means that the ID should not be
  3388. ;; fetched again. In the case of an expired
  3389. ;; article, the header will not be fetched.
  3390. (setq uncached-articles
  3391. (gnus-sorted-nunion fetched-articles
  3392. uncached-articles))
  3393. )))
  3394. ;; Erase the temp buffer
  3395. (set-buffer gnus-agent-overview-buffer)
  3396. (erase-buffer)
  3397. ;; Copy the nntp-server-buffer to the temp buffer
  3398. (set-buffer nntp-server-buffer)
  3399. (copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max))
  3400. ;; Merge the temp buffer with the known headers (found on
  3401. ;; disk in FILE) into the nntp-server-buffer
  3402. (when uncached-articles
  3403. (gnus-agent-braid-nov uncached-articles file))
  3404. ;; Save the new set of known headers to FILE
  3405. (set-buffer nntp-server-buffer)
  3406. (let ((coding-system-for-write
  3407. gnus-agent-file-coding-system))
  3408. (gnus-agent-check-overview-buffer)
  3409. (write-region (point-min) (point-max) file nil 'silent))
  3410. (gnus-agent-update-view-total-fetched-for group t)
  3411. ;; Update the group's article alist to include the newly
  3412. ;; fetched articles.
  3413. (gnus-agent-load-alist group)
  3414. (gnus-agent-save-alist group uncached-articles nil)
  3415. )
  3416. ;; Copy the temp buffer to the nntp-server-buffer
  3417. (set-buffer nntp-server-buffer)
  3418. (erase-buffer)
  3419. (insert-buffer-substring gnus-agent-overview-buffer)))
  3420. (if (and fetch-old
  3421. (not (numberp fetch-old)))
  3422. t ; Don't remove anything.
  3423. (nnheader-nov-delete-outside-range
  3424. (car articles)
  3425. (car (last articles)))
  3426. t)
  3427. 'nov))
  3428. (defun gnus-agent-request-article (article group)
  3429. "Retrieve ARTICLE in GROUP from the agent cache."
  3430. (when (and gnus-agent
  3431. (or gnus-agent-cache
  3432. (not gnus-plugged))
  3433. (numberp article))
  3434. (let* ((gnus-command-method (gnus-find-method-for-group group))
  3435. (file (gnus-agent-article-name (number-to-string article) group))
  3436. (buffer-read-only nil)
  3437. (file-name-coding-system nnmail-pathname-coding-system))
  3438. (when (and (file-exists-p file)
  3439. (> (nth 7 (file-attributes file)) 0))
  3440. (erase-buffer)
  3441. (gnus-kill-all-overlays)
  3442. (let ((coding-system-for-read gnus-cache-coding-system))
  3443. (insert-file-contents file))
  3444. t))))
  3445. (defun gnus-agent-store-article (article group)
  3446. (let* ((gnus-command-method (gnus-find-method-for-group group))
  3447. (file (gnus-agent-article-name (number-to-string article) group))
  3448. (file-name-coding-system nnmail-pathname-coding-system)
  3449. (coding-system-for-write gnus-cache-coding-system))
  3450. (when (not (file-exists-p file))
  3451. (gnus-make-directory (file-name-directory file))
  3452. (write-region (point-min) (point-max) file nil 'silent)
  3453. ;; Tell the Agent when the article was fetched, so that it can
  3454. ;; be expired later.
  3455. (gnus-agent-load-alist group)
  3456. (gnus-agent-save-alist group (list article)
  3457. (time-to-days (current-time))))))
  3458. (defun gnus-agent-regenerate-group (group &optional reread)
  3459. "Regenerate GROUP.
  3460. If REREAD is t, all articles in the .overview are marked as unread.
  3461. If REREAD is a list, the specified articles will be marked as unread.
  3462. In addition, their NOV entries in .overview will be refreshed using
  3463. the articles' current headers.
  3464. If REREAD is not nil, downloaded articles are marked as unread."
  3465. (interactive
  3466. (list (gnus-agent-read-group)
  3467. (catch 'mark
  3468. (while (let (c
  3469. (cursor-in-echo-area t)
  3470. (echo-keystrokes 0))
  3471. (message "Mark as unread: (n)one / (a)ll / all (d)ownloaded articles? (n) ")
  3472. (setq c (read-char-exclusive))
  3473. (cond ((or (eq c ?\r) (eq c ?n) (eq c ?N))
  3474. (throw 'mark nil))
  3475. ((or (eq c ?a) (eq c ?A))
  3476. (throw 'mark t))
  3477. ((or (eq c ?d) (eq c ?D))
  3478. (throw 'mark 'some)))
  3479. (gnus-message 3 "Ignoring unexpected input")
  3480. (sit-for 1)
  3481. t)))))
  3482. (when group
  3483. (gnus-message 5 "Regenerating in %s" (gnus-agent-decoded-group-name group))
  3484. (let* ((gnus-command-method (or gnus-command-method
  3485. (gnus-find-method-for-group group)))
  3486. (file (gnus-agent-article-name ".overview" group))
  3487. (dir (file-name-directory file))
  3488. (file-name-coding-system nnmail-pathname-coding-system)
  3489. (downloaded (if (file-exists-p dir)
  3490. (sort (delq nil (mapcar (lambda (name)
  3491. (and (not (file-directory-p (nnheader-concat dir name)))
  3492. (string-to-number name)))
  3493. (directory-files dir nil "^[0-9]+$" t)))
  3494. '>)
  3495. (progn (gnus-make-directory dir) nil)))
  3496. nov-arts
  3497. alist header
  3498. regenerated)
  3499. (mm-with-unibyte-buffer
  3500. (if (file-exists-p file)
  3501. (let ((nnheader-file-coding-system
  3502. gnus-agent-file-coding-system))
  3503. (nnheader-insert-file-contents file)))
  3504. (set-buffer-modified-p nil)
  3505. ;; Load the article IDs found in the overview file. As a
  3506. ;; side-effect, validate the file contents.
  3507. (let ((load t))
  3508. (while load
  3509. (setq load nil)
  3510. (goto-char (point-min))
  3511. (while (< (point) (point-max))
  3512. (cond ((and (looking-at "[0-9]+\t")
  3513. (<= (- (match-end 0) (match-beginning 0)) 9))
  3514. (push (read (current-buffer)) nov-arts)
  3515. (forward-line 1)
  3516. (let ((l1 (car nov-arts))
  3517. (l2 (cadr nov-arts)))
  3518. (cond ((and (listp reread) (memq l1 reread))
  3519. (gnus-delete-line)
  3520. (setq nov-arts (cdr nov-arts))
  3521. (gnus-message 4 "gnus-agent-regenerate-group: NOV\
  3522. entry of article %s deleted." l1))
  3523. ((not l2)
  3524. nil)
  3525. ((< l1 l2)
  3526. (gnus-message 3 "gnus-agent-regenerate-group: NOV\
  3527. entries are NOT in ascending order.")
  3528. ;; Don't sort now as I haven't verified
  3529. ;; that every line begins with a number
  3530. (setq load t))
  3531. ((= l1 l2)
  3532. (forward-line -1)
  3533. (gnus-message 4 "gnus-agent-regenerate-group: NOV\
  3534. entries contained duplicate of article %s. Duplicate deleted." l1)
  3535. (gnus-delete-line)
  3536. (setq nov-arts (cdr nov-arts))))))
  3537. (t
  3538. (gnus-message 1 "gnus-agent-regenerate-group: NOV\
  3539. entries contained line that did not begin with an article number. Deleted\
  3540. line.")
  3541. (gnus-delete-line))))
  3542. (when load
  3543. (gnus-message 5 "gnus-agent-regenerate-group: Sorting NOV\
  3544. entries into ascending order.")
  3545. (sort-numeric-fields 1 (point-min) (point-max))
  3546. (setq nov-arts nil))))
  3547. (gnus-agent-check-overview-buffer)
  3548. ;; Construct a new article alist whose nodes match every header
  3549. ;; in the .overview file. As a side-effect, missing headers are
  3550. ;; reconstructed from the downloaded article file.
  3551. (while (or downloaded nov-arts)
  3552. (cond ((and downloaded
  3553. (or (not nov-arts)
  3554. (> (car downloaded) (car nov-arts))))
  3555. ;; This entry is missing from the overview file
  3556. (gnus-message 3 "Regenerating NOV %s %d..."
  3557. (gnus-agent-decoded-group-name group)
  3558. (car downloaded))
  3559. (let ((file (concat dir (number-to-string (car downloaded)))))
  3560. (mm-with-unibyte-buffer
  3561. (nnheader-insert-file-contents file)
  3562. (nnheader-remove-body)
  3563. (setq header (nnheader-parse-naked-head)))
  3564. (mail-header-set-number header (car downloaded))
  3565. (if nov-arts
  3566. (let ((key (concat "^" (int-to-string (car nov-arts))
  3567. "\t")))
  3568. (or (re-search-backward key nil t)
  3569. (re-search-forward key))
  3570. (forward-line 1))
  3571. (goto-char (point-min)))
  3572. (nnheader-insert-nov header))
  3573. (setq nov-arts (cons (car downloaded) nov-arts)))
  3574. ((eq (car downloaded) (car nov-arts))
  3575. ;; This entry in the overview has been downloaded
  3576. (push (cons (car downloaded)
  3577. (time-to-days
  3578. (nth 5 (file-attributes
  3579. (concat dir (number-to-string
  3580. (car downloaded))))))) alist)
  3581. (setq downloaded (cdr downloaded))
  3582. (setq nov-arts (cdr nov-arts)))
  3583. (t
  3584. ;; This entry in the overview has not been downloaded
  3585. (push (cons (car nov-arts) nil) alist)
  3586. (setq nov-arts (cdr nov-arts)))))
  3587. ;; When gnus-agent-consider-all-articles is set,
  3588. ;; gnus-agent-regenerate-group should NOT remove article IDs from
  3589. ;; the alist. Those IDs serve as markers to indicate that an
  3590. ;; attempt has been made to fetch that article's header.
  3591. ;; When gnus-agent-consider-all-articles is NOT set,
  3592. ;; gnus-agent-regenerate-group can remove the article ID of every
  3593. ;; article (with the exception of the last ID in the list - it's
  3594. ;; special) that no longer appears in the overview. In this
  3595. ;; situation, the last article ID in the list implies that it,
  3596. ;; and every article ID preceding it, have been fetched from the
  3597. ;; server.
  3598. (if gnus-agent-consider-all-articles
  3599. ;; Restore all article IDs that were not found in the overview file.
  3600. (let* ((n (cons nil alist))
  3601. (merged n)
  3602. (o (gnus-agent-load-alist group)))
  3603. (while o
  3604. (let ((nID (caadr n))
  3605. (oID (caar o)))
  3606. (cond ((not nID)
  3607. (setq n (setcdr n (list (list oID))))
  3608. (setq o (cdr o)))
  3609. ((< oID nID)
  3610. (setcdr n (cons (list oID) (cdr n)))
  3611. (setq o (cdr o)))
  3612. ((= oID nID)
  3613. (setq o (cdr o))
  3614. (setq n (cdr n)))
  3615. (t
  3616. (setq n (cdr n))))))
  3617. (setq alist (cdr merged)))
  3618. ;; Restore the last article ID if it is not already in the new alist
  3619. (let ((n (last alist))
  3620. (o (last (gnus-agent-load-alist group))))
  3621. (cond ((not o)
  3622. nil)
  3623. ((not n)
  3624. (push (cons (caar o) nil) alist))
  3625. ((< (caar n) (caar o))
  3626. (setcdr n (list (car o)))))))
  3627. (let ((inhibit-quit t))
  3628. (if (setq regenerated (buffer-modified-p))
  3629. (let ((coding-system-for-write gnus-agent-file-coding-system))
  3630. (write-region (point-min) (point-max) file nil 'silent)))
  3631. (setq regenerated (or regenerated
  3632. (and reread gnus-agent-article-alist)
  3633. (not (equal alist gnus-agent-article-alist))))
  3634. (setq gnus-agent-article-alist alist)
  3635. (when regenerated
  3636. (gnus-agent-save-alist group)
  3637. ;; I have to alter the group's active range NOW as
  3638. ;; gnus-make-ascending-articles-unread will use it to
  3639. ;; recalculate the number of unread articles in the group
  3640. (let ((group (gnus-group-real-name group))
  3641. (group-active (or (gnus-active group)
  3642. (gnus-activate-group group))))
  3643. (gnus-agent-possibly-alter-active group group-active)))))
  3644. (when (and reread gnus-agent-article-alist)
  3645. (gnus-agent-synchronize-group-flags
  3646. group
  3647. (list (list
  3648. (if (listp reread)
  3649. reread
  3650. (delq nil (mapcar (function (lambda (c)
  3651. (cond ((eq reread t)
  3652. (car c))
  3653. ((cdr c)
  3654. (car c)))))
  3655. gnus-agent-article-alist)))
  3656. 'del '(read)))
  3657. gnus-command-method)
  3658. (when regenerated
  3659. (gnus-agent-update-files-total-fetched-for group nil)))
  3660. (gnus-message 5 "")
  3661. regenerated)))
  3662. ;;;###autoload
  3663. (defun gnus-agent-regenerate (&optional _clean reread)
  3664. "Regenerate all agent covered files.
  3665. CLEAN is obsolete and ignored."
  3666. (interactive)
  3667. (let (regenerated)
  3668. (gnus-message 4 "Regenerating Gnus agent files...")
  3669. (dolist (gnus-command-method (gnus-agent-covered-methods))
  3670. (dolist (group (gnus-groups-from-server gnus-command-method))
  3671. (setq regenerated (or (gnus-agent-regenerate-group group reread)
  3672. regenerated))))
  3673. (gnus-message 4 "Regenerating Gnus agent files...done")
  3674. regenerated))
  3675. (defun gnus-agent-go-online (&optional force)
  3676. "Switch servers into online status."
  3677. (interactive (list t))
  3678. (dolist (server gnus-opened-servers)
  3679. (when (eq (nth 1 server) 'offline)
  3680. (if (if (eq force 'ask)
  3681. (gnus-y-or-n-p
  3682. (format "Switch %s:%s into online status? "
  3683. (caar server) (cadar server)))
  3684. force)
  3685. (setcar (nthcdr 1 server) 'close)))))
  3686. (defun gnus-agent-toggle-group-plugged (group)
  3687. "Toggle the status of the server of the current group."
  3688. (interactive (list (gnus-group-group-name)))
  3689. (let* ((method (gnus-find-method-for-group group))
  3690. (status (cadr (assoc method gnus-opened-servers))))
  3691. (if (eq status 'offline)
  3692. (gnus-server-set-status method 'closed)
  3693. (gnus-close-server method)
  3694. (gnus-server-set-status method 'offline))
  3695. (message "Turn %s:%s from %s to %s." (car method) (cadr method)
  3696. (if (eq status 'offline) 'offline 'online)
  3697. (if (eq status 'offline) 'online 'offline))))
  3698. (defun gnus-agent-group-covered-p (group)
  3699. (gnus-agent-method-p (gnus-group-method group)))
  3700. (defun gnus-agent-update-files-total-fetched-for (group delta
  3701. &optional method path)
  3702. "Update, or set, the total disk space used by the articles that the
  3703. agent has fetched."
  3704. (when gnus-agent-total-fetched-hashtb
  3705. (gnus-agent-with-refreshed-group
  3706. group
  3707. ;; if null, gnus-agent-group-pathname will calc method.
  3708. (let* ((gnus-command-method method)
  3709. (path (or path (gnus-agent-group-pathname group)))
  3710. (entry (or (gnus-gethash path gnus-agent-total-fetched-hashtb)
  3711. (gnus-sethash path (make-list 3 0)
  3712. gnus-agent-total-fetched-hashtb)))
  3713. (file-name-coding-system nnmail-pathname-coding-system))
  3714. (when (file-exists-p path)
  3715. (when (listp delta)
  3716. (if delta
  3717. (let ((sum 0.0)
  3718. file)
  3719. (while (setq file (pop delta))
  3720. (incf sum (float (or (nth 7 (file-attributes
  3721. (nnheader-concat
  3722. path
  3723. (if (numberp file)
  3724. (number-to-string file)
  3725. file)))) 0))))
  3726. (setq delta sum))
  3727. (let ((sum (- (nth 2 entry)))
  3728. (info (directory-files-and-attributes
  3729. path nil "^-?[0-9]+$" t))
  3730. file)
  3731. (while (setq file (pop info))
  3732. (incf sum (float (or (nth 8 file) 0))))
  3733. (setq delta sum))))
  3734. (setq gnus-agent-need-update-total-fetched-for t)
  3735. (incf (nth 2 entry) delta))))))
  3736. (defun gnus-agent-update-view-total-fetched-for
  3737. (group agent-over &optional method path)
  3738. "Update, or set, the total disk space used by the .agentview and
  3739. .overview files. These files are calculated separately as they can be
  3740. modified."
  3741. (when gnus-agent-total-fetched-hashtb
  3742. (gnus-agent-with-refreshed-group
  3743. group
  3744. ;; if null, gnus-agent-group-pathname will calc method.
  3745. (let* ((gnus-command-method method)
  3746. (path (or path (gnus-agent-group-pathname group)))
  3747. (entry (or (gnus-gethash path gnus-agent-total-fetched-hashtb)
  3748. (gnus-sethash path (make-list 3 0)
  3749. gnus-agent-total-fetched-hashtb)))
  3750. (file-name-coding-system nnmail-pathname-coding-system)
  3751. (size (or (nth 7 (file-attributes
  3752. (nnheader-concat
  3753. path (if agent-over
  3754. ".overview"
  3755. ".agentview"))))
  3756. 0)))
  3757. (setq gnus-agent-need-update-total-fetched-for t)
  3758. (setf (nth (if agent-over 1 0) entry) size)))))
  3759. (defun gnus-agent-total-fetched-for (group &optional method no-inhibit)
  3760. "Get the total disk space used by the specified GROUP."
  3761. (unless (equal group "dummy.group")
  3762. (unless gnus-agent-total-fetched-hashtb
  3763. (setq gnus-agent-total-fetched-hashtb (gnus-make-hashtable 1024)))
  3764. ;; if null, gnus-agent-group-pathname will calc method.
  3765. (let* ((gnus-command-method method)
  3766. (path (gnus-agent-group-pathname group))
  3767. (entry (gnus-gethash path gnus-agent-total-fetched-hashtb)))
  3768. (if entry
  3769. (apply '+ entry)
  3770. (let ((gnus-agent-inhibit-update-total-fetched-for (not no-inhibit)))
  3771. (+
  3772. (gnus-agent-update-view-total-fetched-for group nil method path)
  3773. (gnus-agent-update-view-total-fetched-for group t method path)
  3774. (gnus-agent-update-files-total-fetched-for group nil method path)))))))
  3775. (provide 'gnus-agent)
  3776. ;;; gnus-agent.el ends here