AUTHORS0000664000175000017500000000176712072264260012020 0ustar patrickpatrickMaintainer: =========== Patrick Ulbrich Related software: ================= Even though many parts of Mailnag have completely been rewritten by now, Mailnag started out as a fork of Popper (http://launchpad.net/popper). Popper was written by Ralf Hersel . Contributors: ============= Edwin Smulders Leighton Earl Taylor Braun-Jones Thorsten Leemhuis Vincent Cheng Translators (launchpad): ======================== Adolfo Jayme Barrientos Alin Andrei AmiG Akihiro Tsukada Asier Sarasua Garmendia Bae Taegil Dmitry Shachnev Eugene Marshal Einar Uvsløkk Fitoschido Hromin Ivo Majić javiggvv Jirka Dutka KEIII LEROY Jean-Christophe Lê Trường An Lidinei Lukasz Manuel Xosé Lemos Marti Bosch Mattia Meneguzzo Мирослав Николић Patrick Ulbrich Pedro Beja Philippe Poumaroux Rafael Neri Rax u-t vbert Vyacheslav Sharmanov Wolter Hellmund Zeppelinlg 朱涛 data/0000775000175000017500000000000012072264260011646 5ustar patrickpatrickdata/mailnag_config.10000664000175000017500000000113712072264260014667 0ustar patrickpatrick.TH MAILNAG_CONFIG "1" "November 2012" "Mailnag 0.5.0" .SH NAME mailnag_config \- a helper script to setup Mailnag .SH SYNOPSIS \fBmailnag_config\fP .SH DESCRIPTION \fBmailnag_config\fP is a script that makes it easy to setup Mailnag initially. This only needs to be run once; after running it and closing the configuration window, Mailnag will be started automatically. .SH SEE ALSO .PP \fBmailnag\fP(1) .SH AUTHOR \fBmailnag\fP was written by Patrick Ulbrich . .PP This manual page was written by Vincent Cheng , for the Debian project (and may be used by others). data/config_window.ui0000664000175000017500000006602512072264260015052 0ustar patrickpatrick 1 999 5 1 10 360 False Mailnag Configuration False True True True False 6 6 True True in True True liststore_accounts False False True False True True 0 True False 6 gtk-add True True True True True True 0 gtk-edit True False True True True True True 1 gtk-remove True False True True True True True 2 False False 1 True False Accounts False True False start 6 6 6 6 4 2 6 6 Play sound on new mails True True False 0 True 2 2 3 Start Mailnag automatically True True False 0 True 2 3 4 True False 0 True False start 0 Check interval: 1 2 GTK_FILL True False 6 True True True 0.10000000149011612 adjustment1 True False True 0 True False 0 minutes True True 1 1 2 1 2 True False 0 True False start 0 Notification mode: GTK_FILL True False liststore_notification_mode 1 2 1 True False General 1 False True False 6 vertical 6 Enable spamfilter True True False 0 True False True 0 True True in True False True textbuffer_filter True True 1 2 True False Spamfilter 2 False True False 6 vertical 6 True True False 0 True True False 0 <b>On mail check</b> True False True 0 True False 12 18 vertical 6 True False 0 The following script will be executed on every single mail check. Mailnag passes the total count of new mails to this script, followed by <i>"&lt;sender&gt; &lt;subject&gt;"</i> pairs. True False True 0 True False False False True 1 False True 1 True True False 0 True True False 0 <b>On mail arrival</b> True False True 2 True False 12 18 vertical 6 True False 0 The following script will be executed whenever new mails arrive. Mailnag passes the total count of new mails to this script, followed by <i>"&lt;sender&gt; &lt;subject&gt;"</i> pairs. True False True 0 True False False False True 1 False True 3 3 True False Events 3 False True False 24 True False vertical False False 0 True False APP_DESC True False False 1 False False 0 True True <a href="https://github.com/pulb/mailnag">Mailnag</a> - A mail notifier for GNOME 3 Copyright (c) 2011, 2012 Patrick Ulbrich and contributors. True False False 1 4 True False About 4 False Single Summary data/account_dialog.ui0000664000175000017500000004255612072264260015174 0ustar patrickpatrick False 5 Mail Account False True dialog False vertical 18 False end gtk-cancel True True True False True True True 0 gtk-ok True False True True False True True True 1 False True end 0 True False 6 6 6 6 6 9 2 True False 0 True False 0 Port: 0 5 1 1 True False 0 True False 0 Server: 0 4 1 1 True False 0 True False 0 Password: 0 3 1 1 True False 0 True False 0 User: 0 2 1 1 True False 0 True False 0 Accountname: 0 1 1 1 True True True True 1 5 1 1 True True True True 1 4 1 1 True True True False True 1 3 1 1 True True True True 1 2 1 1 True True True True 1 1 1 1 True False 0 True False 0 Folders: 0 6 1 1 True True True True 1 6 1 1 Enable Push-IMAP True True False False 0 True 0 7 2 1 True False 0 True False 0 Account type: 0 0 1 1 True False POP3 IMAP 1 0 1 1 Enable SSL encryption True True False False 0 True 0 8 2 1 True True 1 button_cancel button_save data/mailnag.ogg0000664000175000017500000003563612072264260013771 0ustar patrickpatrickOggSaSvorbisOggSa5=vorbis-Xiph.Org libVorbis I 20101101 (Schaufenugget)vorbis%BCV@$s*FsBPBkBL2L[%s!B[(АU@AxA!%=X'=!9xiA!B!B!E9h'A08 8E9X'A B9!$5HP9,(05(0ԃ BI5gAxiA!$AHAFAX9A*9 4d((  @Qqɑɱ  YHHH$Y%Y%Y扪,˲,˲,2 HPQ Eq Yd8Xh爎4CSR,1\wD3$ R1s9R9sBT1ƜsB!1sB!RJƜsB!RsB!J)sB!B)B!J(B!BB!RB(!R!B)%R !RBRJ)BRJ)J %R))J!RJJ)TJ J)%RJ!J)8A'Ua BCVdR)-E"KFsPZr RͩR $1T2B BuL)-BrKsA3stG DfDBpxP S@bB.TX\]\@.!!A,pox N)*u \adhlptx||$%@DD4s !"#$ OggSBa':5 |{Hwt0Z!r<%‰{\9wMԪUn!03=1[iUA_uw+h7q)ن;ޮ9't{[āCWꡌ o>`?EJ("$2 a!AP[ܠIR5]TM r%C*]b}ݫ ꃇ eg 1T?=8䤸Vz}{0h]ʊ5XJ8\ݢ;_ -8Fe<@֙_Gdyf_b8w[u95 Z ցV` C( z@?W`u Ֆn$rJ&(ޤ)W'ASQL @Gnb?tp"^ @5>]- !8XJ 6Z10 Q*1Ph6w{~HX/^M͟w#- t`J&dk" `CF " FB3M6 =qY_i2#0ՈnI- 8l5o v>yl$50q (Tj ld@nKLif5GqQSvԻ{3j`Wb$`E<$6!@h,U } ؗ6H86I H1n'u i @"N_58 d4;T cŽU. {;=R,&0b Q,"Z,:T 8."uW`}SBxNnv[{]$j/W?;L;m XnT*I!BcAfPdP T[khqk1z/݁1~TAM~NK Ndc; WH*#mtdHµB + \ p@~ܥ;xQ~C)zu#k3;呭DMw / X&oZa&p( (6+9g vP ivl֑M]SՌ[a b<߽^>o8 0Ar{QCR, 2CَHɓJ3)ŇD 3[KPE:iravmcnx5zw@  y!?> ^c P-dU 4 >i.]x6 5=˾P|ϴ]M|W~f;F%q($ @'*&Й c  \Р%8b''j ^xɞ%'_ǣG\|5җ+(V z&RҶg+iQaIC!t Y,n~BkyYyWyҏ4I^~C6%5$Pb0_>zU?>OY1^c4?=2D`{"``g2\c`~y^wú(0ſ t|×@fGD!@r/0XH240dI.]q)]kx@>3yUQ;K߾o0xU@S%XXW9  ڤϿ E˛zRд+=p#Boe! @ V?@U}c=AU;};G'&:}1D$ȿB|0q d5 3l s(%8n*yg]zQ 5@xp-vC @>@x#"8 T@ K19g:[omJ$S*e%M I.|X~fl*ce`cKX*@`@L2qhd(.Ĭ#}k7j8= w;KԆxy Xߝ`vT*3!*Hu9 -Au<n ?x0kHն cyTҊPDP\tn ml;0K0I\|[ah$lhT-(d޷-bϣDɻcDMq4Hj1~0'.~ UUU@:sz*SғKg{yQ"ҬX!phjPl @L.й8Q nc͝Yd]P$g|f|,N?Lݠ g%}=i^"p1k?d04X!sqg^Ӛss>gM2O v-KJUğ"(v1Y "- /XUUEX+<ܷ[گ7'NB(:x֏ e#;\ (:ZPr)iOZɵN6YnfA&)C_0שռI74S([Ff k+sǐQfn/| /T9TUC|=:%r@3ժF &(>g"TI?kv"ժ((()wbԘLoe0;YOrUUUD^ϼښfX۷o֯]=qu7LJ+J!*"0y*4?1մgn@)W! dC=&w_8:{ E66/X!ߩB`1g*%P\tXw yJ1ӆDm4p^w2mBmA!;d=Q Q#䮝{xQ{0K*m0TUІfѻ*x<#xݏ;<8vWrpgpaG*Р@p Xk8>9$tf' S _v}]gwt7#0LΉhzbѴyn7кji'Q);lV<>ד#2_b4cTn#Gd<ߖˤJL:;-B;Q}]rU\VZ* ҝPdtԈ'V/~P;Р3b*+:#>s9S7 y2g$B@Hl2rjmgY[>(A_tS?7b{i|S'ݖǹz_^v}?ݪ.͔ ٻcY=[&V070ic.\`oCV =sGKSc2x{ :iq =T@Γ@(3S@J6ε88؁ᝇ (6$T,C7 )|Kj6`ݗE{04Dzbq.X%F{^9wcWV:W>IR_&ߗIiaפoR|֏U7ȧqwd4GxG%ïMR-jg+dKU<@aUUUu@{bguBBP2\>u>npb=)9xn7C=k}hE0JӎxEovsvL߁f;S`;,U.`'TA|?g:mp)pi&# *ހ.JVNRJBwWv;J_8ײ4!Dy65%/x^7EjE97TUe3D-p̎\F<-De690 [7@g&3:s;~Ԕۜf}uOU~iQ;wޡζ,8 qm_~J!Ӎ8,P5r(f{RSi2՞H{qx3j ^gf$WyfN_\\;ZְI.,6+P#%8+_/FfŪx <2 xPTwTш!D0aL9_T%K?_T9 g;_ɜfhjoy,s93gVg=\ݐ8bwvCN_ߘ}ѻru lu.dRc UGw9E*4"s2U=s@W vD|f"jîeW)('!EnjDVy+oeFXUUVB[\`^7"4|Xf3+`:nB!SfgSp(tTT>f쪓avܖrbWEl57CUHҴµE^8̜Ӭשּׂ`UP贌܅RfU#MSPn*5Ғ @CF 6$;m%W5ўMHg +WlDbr B"N%HW df*JNBeʷ4߾M@u}RI؊BAR؜Saާ1g}8PO>urٰ740`Va:]uWjd}ዳkOBBԇ̇E# jվ`2K\+[OG돼7OFsQqcwڌ7}Z~=$ROt-ǠHvD{H5/@i7Pn#C9@Ѓ[B@Gv4/vHW!Kq-4 ;lpa> ?/6>}'I ͆i2 s&mۿ<-3B*gjpzT^+N/. cYF^Iw߲ )}"!NbM]6 ;1r֢}kҶUo6sm#& ~8)Fr#?jJ%"jjjLV:eLBBZ;8Y%/[(`7`NEV{3ѝu8uN+9ƘW{~:{xM̥״9E~9|l=fpf(4 ٰϿd~ir]k])so'vkEG1u~YgibW./\;>B4l!cOBQ4P3ZvHrX<%JF1XAFK,PUU*B:gJnN @ Of_LhY}N@=U{;;=iN1|+{_c;ޘaz=3k%Thw4?*c &bV&^uk@ƈ ='l99sI;XaθjBsBM빈35j!Ŝ`ѢU钦 7IH1{u~e媪{eM@n=G Ui$)o4II>\}N37}}6l;7':`ûްg69497ro`6$AOOYuo%8HXݘI̺vLz芡YuB[GľY(`PI֣!nSF7Uys @M m)j&_ˉO³"LDq^\篡$#SJ(Ne3>|gJXM`e:7lm~mu|?yN%'k3V_0IS90u}7C}ӟ:g}k: \;ȹ[dƄP+&v+jH0ͯ ly2<\9i9p,~'<ծsWvpo@CR-[ԭowI&΅hgON;m33j_B@?lo95VUp޵3IrOHQ2F5 ]$!D|fCHk:jH\UU ~_ S2J8Z"Sۺ]Mմ.:6a;:5C=?$~>_󽈟S3͟s|uFؾIаvq|@ogf]@ͅGruP+@>/ #QOӊ:P#מYN.PhGa;3?z71Q d]jP͔.Ik9]P. .PP This manual page was written by Vincent Cheng , for the Debian project (and may be used by others). data/mailnag_config.desktop0000664000175000017500000000044712072264260016203 0ustar patrickpatrick [Desktop Entry] Name=Mailnag Configuration Name[de]=Mailnag Configuration Comment=A notifier for new emails Comment[de]=Ein Benachrichtiger für neue Emails Exec=/usr/bin/mailnag_config Icon=/usr/share/mailnag/mailnag.svg Terminal=false Type=Application Categories=Network; StartupNotify=false data/mailnag.svg0000664000175000017500000003570612072264260014012 0ustar patrickpatrick image/svg+xml gen_locales0000775000175000017500000000102012072264260013127 0ustar patrickpatrick#/bin/bash # # script to generate mailnag locales # APPNAME=mailnag PO_DIR=po LOCALE_DIR=locale # /usr/share/locale MKDIR_ERR=1 MSGFMT_ERR=2 # check if a custom locale dir # was passed as a commandline arg if [ $# -gt 0 ]; then LOCALE_DIR=$1 fi for f in `ls $PO_DIR/*.po`; do LANG=`basename ${f%".po"}` DEST_DIR=$LOCALE_DIR/$LANG/LC_MESSAGES if [ ! -d $DEST_DIR ]; then mkdir -p $DEST_DIR || exit $MKDIR_ERR fi echo "creating $DEST_DIR/$APPNAME.mo" msgfmt -o $DEST_DIR/$APPNAME.mo $f || exit $MSGFMT_ERR done gen_po_template0000775000175000017500000000107312072264260014026 0ustar patrickpatrick#!/bin/bash # extracts strings from *.py and .ui files and # generates a gettext .pot template. glade_dir=./data python_dir=./Mailnag pot_file=./po/mailnag.pot if [ ! -d ./po ]; then mkdir ./po fi if [ -f $pot_file ]; then rm $pot_file fi # generate string headers of all glade files for f in $glade_dir/*.ui ; do intltool-extract --type=gettext/glade $f done # write template files pyfiles=`find $python_dir -iname "*.py" -printf "%p "` xgettext $pyfiles $glade_dir/*.h --keyword=_ --keyword=N_ --from-code=UTF-8 --output=$pot_file # clean up rm $glade_dir/*.h LICENSE0000664000175000017500000004325412072264260011752 0ustar patrickpatrick GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. mailnag0000775000175000017500000000102012072264260012264 0ustar patrickpatrick#!/bin/bash LIB_DIR=./Mailnag config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/mailnag" mkdir --parents "$config_dir" if [ -f "$config_dir/mailnag.pid" ]; then pid=$(cat "$config_dir/mailnag.pid") if [ "`ps -p $pid -o comm=`" == "mailnag" ]; then kill $pid 2> /dev/null # wait until mailnag teminates and removes it's pid file while ps -p $pid > /dev/null; do sleep 1 done fi fi rm --force "$config_dir/mailnag.log" cd $(dirname $(readlink -f $0)) python $LIB_DIR/mailnag.py >> "$config_dir/mailnag.log" 2>&1 & Mailnag/0000775000175000017500000000000012072264260012305 5ustar patrickpatrickMailnag/mailnag.py0000664000175000017500000001075512072264260014277 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # mailnag.py # # Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Leighton Earl # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # import os from gi.repository import GObject, GLib import time import signal import traceback from common.config import read_cfg, cfg_exists, cfg_folder from common.utils import set_procname, is_online from common.accountlist import AccountList from daemon.mailchecker import MailChecker from daemon.idlers import Idlers mainloop = None mailchecker = None idlers = None def read_config(): if not cfg_exists(): return None else: return read_cfg() def write_pid(): pid_file = os.path.join(cfg_folder, 'mailnag.pid') f = open(pid_file, 'w') f.write(str(os.getpid())) f.close() def delete_pid(): pid_file = os.path.join(cfg_folder, 'mailnag.pid') if os.path.exists(pid_file): os.remove(pid_file) # Workaround: # sometimes gnomeshell's notification server (org.freedesktop.Notifications implementation) # doesn't seem to be up immediately upon session start, so prevent Mailnag from crashing # by checking if the org.freedesktop.Notifications DBUS interface is available yet. # See https://github.com/pulb/mailnag/issues/48 def wait_for_notification_server(): import dbus bus = dbus.SessionBus() while True: try: notify = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications') iface = dbus.Interface(notify, 'org.freedesktop.Notifications') inf = iface.GetServerInformation() if inf[0] == u'gnome-shell': break except: pass print 'Waiting for GNOME-Shell notification server...' time.sleep(5) def wait_for_inet_connection(): if not is_online(): print 'Waiting for internet connection...' while not is_online(): time.sleep(5) def cleanup(): # clean up resources if mailchecker != None: mailchecker.dispose() if idlers != None: idlers.dispose() delete_pid() def sig_handler(signum, frame): if mainloop != None: mainloop.quit() def main(): global mainloop, mailchecker, idlers set_procname("mailnag") GObject.threads_init() signal.signal(signal.SIGTERM, sig_handler) try: # write Mailnag's process id to file write_pid() cfg = read_config() if (cfg == None): print 'Error: Cannot find configuration file. Please run mailnag_config first.' exit(1) wait_for_notification_server() wait_for_inet_connection() accounts = AccountList() accounts.load_from_cfg(cfg, enabled_only = True) mailchecker = MailChecker(cfg) # immediate check, check *all* accounts try: mailchecker.check(accounts) except: traceback.print_exc() idle_accounts = filter(lambda acc: acc.imap and acc.idle, accounts) non_idle_accounts = filter(lambda acc: (not acc.imap) or (acc.imap and not acc.idle), accounts) # start polling thread for POP3 accounts and # IMAP accounts without idle support if len(non_idle_accounts) > 0: def poll_func(): try: mailchecker.check(non_idle_accounts) except: traceback.print_exc() return True check_interval = int(cfg.get('general', 'check_interval')) GObject.timeout_add_seconds(60 * check_interval, poll_func) # start idler threads for IMAP accounts with idle support if len(idle_accounts) > 0: def sync_func(account): try: mailchecker.check([account]) except: traceback.print_exc() idlers = Idlers(idle_accounts, sync_func) idlers.run() mainloop = GObject.MainLoop() mainloop.run() except KeyboardInterrupt: pass # ctrl+c pressed finally: cleanup() if __name__ == '__main__': main() Mailnag/config.py0000664000175000017500000000220112072264260014117 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # config.py # # Copyright 2011 Patrick Ulbrich # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # from gi.repository import Gtk from common.utils import set_procname from configuration.configwindow import ConfigWindow def main(): set_procname("mailnag_config") confwin = ConfigWindow() Gtk.main() if __name__ == "__main__": main() Mailnag/common/0000775000175000017500000000000012072264260013575 5ustar patrickpatrickMailnag/common/keyring.py0000664000175000017500000001104412072264260015617 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # keyring.py # # Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # from gi.repository import GnomeKeyring from common.i18n import _ from common.account import Account class Keyring: def __init__(self): self.KEYRING_ITEM_NAME = 'Mailnag password for %s://%s@%s' (result, kr_name) = GnomeKeyring.get_default_keyring_sync() self._defaultKeyring = kr_name if self._defaultKeyring == None: self._defaultKeyring = 'login' result = GnomeKeyring.unlock_sync(self._defaultKeyring, None) if (result != GnomeKeyring.Result.OK): raise Exception('Failed to unlock default keyring') # get password for account from Gnome Keyring def get(self, protocol, user, server): (result, ids) = GnomeKeyring.list_item_ids_sync(self._defaultKeyring) if result == GnomeKeyring.Result.OK: displayNameDict = {} for identity in ids: (result, item) = GnomeKeyring.item_get_info_sync(self._defaultKeyring, identity) displayNameDict[item.get_display_name()] = identity if self.KEYRING_ITEM_NAME % (protocol, user, server) in displayNameDict: (result, item) = GnomeKeyring.item_get_info_sync(self._defaultKeyring, \ displayNameDict[self.KEYRING_ITEM_NAME % \ (protocol, user, server)]) if item.get_secret() != '': return item.get_secret() else: # DEBUG print "Keyring.get(): No Keyring Password for %s://%s@%s." % (protocol, user, server) return '' else: # DEBUG print "Keyring.get(): %s://%s@%s not in Keyring." % (protocol, user, server) return '' else: # DEBUG print "Keyring.get(): Neither default- nor 'login'-Keyring available." return '' # store password in Gnome-Keyring def set(self, protocol, user, server, password): if password != '': displayNameDict = {} (result, ids) = GnomeKeyring.list_item_ids_sync(self._defaultKeyring) for identity in ids: (result, item) = GnomeKeyring.item_get_info_sync(self._defaultKeyring, identity) displayNameDict[item.get_display_name()] = identity attrs = GnomeKeyring.Attribute.list_new() GnomeKeyring.Attribute.list_append_string(attrs, 'application', 'Mailnag') GnomeKeyring.Attribute.list_append_string(attrs, 'protocol', protocol) GnomeKeyring.Attribute.list_append_string(attrs, 'user', user) GnomeKeyring.Attribute.list_append_string(attrs, 'server', server) if self.KEYRING_ITEM_NAME % (protocol, user, server) in displayNameDict: (result, item) = GnomeKeyring.item_get_info_sync(self._defaultKeyring, \ displayNameDict[self.KEYRING_ITEM_NAME % \ (protocol, user, server)]) if password != item.get_secret(): GnomeKeyring.item_create_sync(self._defaultKeyring, \ GnomeKeyring.ItemType.GENERIC_SECRET, \ self.KEYRING_ITEM_NAME % (protocol, user, server), \ attrs, password, True) else: GnomeKeyring.item_create_sync(self._defaultKeyring, \ GnomeKeyring.ItemType.GENERIC_SECRET, \ self.KEYRING_ITEM_NAME % (protocol, user, server), \ attrs, password, True) # delete obsolete entries from Keyring def remove(self, accounts): # create list of all valid accounts valid_accounts = [] for acc in accounts: protocol = 'imap' if acc.imap else 'pop' valid_accounts.append(self.KEYRING_ITEM_NAME % \ (protocol, acc.user, acc.server)) # find and delete invalid entries (result, ids) = GnomeKeyring.list_item_ids_sync(self._defaultKeyring) if result == GnomeKeyring.Result.OK: displayNameDict = {} for identity in ids: (result, item) = GnomeKeyring.item_get_info_sync(self._defaultKeyring, identity) displayNameDict[item.get_display_name()] = identity for key in displayNameDict.keys(): if key.startswith('Mailnag password for') \ and key not in valid_accounts: GnomeKeyring.item_delete_sync(self._defaultKeyring, displayNameDict[key]) Mailnag/common/i18n.py0000664000175000017500000000216512072264260014732 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # i18n.py # # Copyright 2011, 2012 Patrick Ulbrich # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # import locale import gettext from common.dist_cfg import PACKAGE_NAME, LOCALE_DIR # bind textdomain for GTK Builder locale.bindtextdomain(PACKAGE_NAME, LOCALE_DIR) # add gettext shortcut "_" for string translations _ = gettext.translation(domain = PACKAGE_NAME, localedir = LOCALE_DIR, fallback = True).ugettext Mailnag/common/config.py0000664000175000017500000000347012072264260015420 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # config.py # # Copyright 2011, 2012 Patrick Ulbrich # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # import os import xdg.BaseDirectory as bd from ConfigParser import RawConfigParser from common.i18n import _ mailnag_defaults = { 'general': { 'check_interval' : '5', 'notification_mode' : '0', 'sender_format' : '1', 'playsound' : '1', 'soundfile' : 'mailnag.ogg', 'autostart' : '1' }, 'filter': { 'filter_enabled' : '0', 'filter_text' : 'newsletter, viagra' }, 'script': { 'script0_enabled' : '0', 'script1_enabled' : '0', 'script0_file' : '', 'script1_file' : '' } } cfg_folder = os.path.join(bd.xdg_config_home, "mailnag") cfg_file = os.path.join(cfg_folder, "mailnag.cfg") def cfg_exists(): return os.path.exists(cfg_file) def read_cfg(): cfg = RawConfigParser() cfg._sections = mailnag_defaults # HACK : use cfg.read_dict(mailnag_defaults) in python 3 if os.path.exists(cfg_file): cfg.read(cfg_file) return cfg def write_cfg(cfg): if not os.path.exists(cfg_folder): os.makedirs(cfg_folder) with open(cfg_file, 'wb') as configfile: cfg.write(configfile) Mailnag/common/utils.py0000664000175000017500000000552412072264260015315 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # utils.py # # Copyright 2011, 2012 Patrick Ulbrich # Copyright 2007 Marco Ferragina # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # import xdg.BaseDirectory as base from gi.repository import Gst, Gio import threading import os import time import urllib2 from common.dist_cfg import PACKAGE_NAME def get_data_file(filename): """ Return path to @filename if it exists anywhere in the data paths, else return None """ # Add "./data" in workdir for running from builddir data_paths = [] data_paths.append("./data") data_paths.extend(base.load_data_paths(PACKAGE_NAME)) for direc in data_paths: file_path = os.path.join(direc, filename) if os.path.exists(file_path): return file_path return None def set_procname(newname): from ctypes import cdll, byref, create_string_buffer libc = cdll.LoadLibrary('libc.so.6') buff = create_string_buffer(len(newname)+1) buff.value = newname libc.prctl(15, byref(buff), 0, 0, 0) def get_default_mail_reader(): mail_reader = None app_info = Gio.AppInfo.get_default_for_type ("x-scheme-handler/mailto", False) if app_info != None: executable = Gio.AppInfo.get_executable(app_info) if (executable != None) and (len(executable) > 0): mail_reader = executable return mail_reader # check for internet connection def is_online(): try: urllib2.urlopen("http://www.google.com/") return True except: return False class _GstPlayThread(threading.Thread): def __init__(self, ply): self.ply = ply threading.Thread.__init__(self) def run(self): def on_eos(bus, msg): # print "EOS" # debug self.ply.set_state(Gst.State.NULL) return True bus = self.ply.get_bus() bus.add_signal_watch() bus.connect('message::eos', on_eos) self.ply.set_state(Gst.State.PLAYING) _gst_initialized = False def gstplay(filename): global _gst_initialized if not _gst_initialized: Gst.init(None) _gst_initialized = True try: cwd = os.getcwd() location = os.path.join(cwd, filename) ply = Gst.ElementFactory.make("playbin", "player") ply.set_property("uri", "file://" + location) pt = _GstPlayThread(ply) pt.start() except: pass Mailnag/common/account.py0000664000175000017500000000671012072264260015607 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # account.py # # Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # import poplib import daemon.imaplib2 as imaplib from common.i18n import _ account_defaults = { 'enabled' : '0', 'name' : '', 'user' : '', 'server' : '', 'port' : '', 'ssl' : '1', 'imap' : '0', 'idle' : '0', 'folder' : '' } class Account: def __init__(self, enabled = False, name = _('Unnamed'), user = '', \ password = '', server = '', port = '', ssl = True, imap = False, idle = False, folder = '' ): self.enabled = enabled # bool self.name = name self.user = user self.password = password self.server = server self.port = port self.ssl = ssl # bool self.imap = imap # bool self.idle = idle # bool self.folder = folder self._conn = None def get_connection(self, use_existing = False): # get email server connection if self.imap: return self._get_IMAP_connection(use_existing) else: return self._get_POP3_connection(use_existing) def get_id(self): # TODO : this id is not really unique... return str(hash(self.user + self.server + self.folder)) def _get_IMAP_connection(self, use_existing): # try to reuse existing connection if use_existing and (self._conn != None) and \ (self._conn.state != imaplib.LOGOUT) and (not self._conn.Terminate): return self._conn self._conn = conn = None try: if self.ssl: if self.port == '': conn = imaplib.IMAP4_SSL(self.server) else: conn = imaplib.IMAP4_SSL(self.server, self.port) else: if self.port == '': conn = imaplib.IMAP4(self.server) else: conn = imaplib.IMAP4(self.server, self.port) conn.login(self.user, self.password) self._conn = conn except: print "Error: Cannot connect to IMAP account: %s. " % self.server try: if conn != None: # conn.close() # allowed in SELECTED state only conn.logout() except: pass return self._conn def _get_POP3_connection(self, use_existing): # try to reuse existing connection if use_existing and (self._conn != None) and ('sock' in self._conn.__dict__): return self._conn self._conn = conn = None try: if self.ssl: if self.port == '': conn = poplib.POP3_SSL(self.server) else: conn = poplib.POP3_SSL(self.server, self.port) else: if self.port == '': conn = poplib.POP3(self.server) else: conn = poplib.POP3(self.server, self.port) conn.getwelcome() conn.user(self.user) conn.pass_(self.password) self._conn = conn except: print "Error: Cannot connect to POP account: %s. " % self.server try: if conn != None: conn.quit() except: pass return self._conn Mailnag/common/dist_cfg.py0000664000175000017500000000342212072264260015732 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # dist_cfg.py # # Copyright 2012, 2013 Patrick Ulbrich # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # # This file contains variables that need to be adjusted for propper distro integration. # Additionally to those variables, packagers have to adjust the following paths: # * LOCALE_DIR in file gen_locales # * LIB_DIR in bash scripts mailnag and mailnag_cfg # * Exec and Icon paths in data/mailnag_config.desktop # Application version displayed in the # about dialog of the config window. APP_VERSION = "0.5.2" # The PACKAGE_NAME variable is used to configure # 1) the path where all app data (glade files, images) is loaded from # (usually /usr/share/) via get_data_file() (see utils.py). # 2) paths for localization files generated with gen_locales # (usually /usr/share/locale//LC_MESSAGES/.mo). # Typically, there's no need to touch this variable. PACKAGE_NAME = "mailnag" # The LOCALE_DIR variable specifies the root path for localization files # (usually you have to make it point to '/usr/share/locale'). LOCALE_DIR = './locale' Mailnag/common/accountlist.py0000664000175000017500000000633012072264260016501 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # accountlist.py # # Copyright 2011 Patrick Ulbrich # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # from common.account import Account, account_defaults from common.keyring import Keyring class AccountList(list): def __init__(self): self._keyring = Keyring() def load_from_cfg(self, cfg, enabled_only=False): del self[:] i = 1 section_name = "Account" + str(i) while cfg.has_section(section_name): enabled = bool(int( self._get_account_cfg(cfg, section_name, 'enabled') )) if (not enabled_only) or (enabled_only and enabled): name = self._get_account_cfg(cfg, section_name, 'name') user = self._get_account_cfg(cfg, section_name, 'user') server = self._get_account_cfg(cfg, section_name, 'server') port = self._get_account_cfg(cfg, section_name, 'port') ssl = bool(int( self._get_account_cfg(cfg, section_name, 'ssl') )) imap = bool(int( self._get_account_cfg(cfg, section_name, 'imap') )) idle = bool(int( self._get_account_cfg(cfg, section_name, 'idle') )) folder = self._get_account_cfg(cfg, section_name, 'folder') protocol = 'imap' if imap else 'pop' password = self._keyring.get(protocol, user, server) acc = Account(enabled, name, user, password, server, port, ssl, imap, idle, folder) self.append(acc) i = i + 1 section_name = "Account" + str(i) def save_to_cfg(self, cfg): # remove existing accounts from cfg i = 1 section_name = "Account" + str(i) while cfg.has_section(section_name): cfg.remove_section(section_name) i = i + 1 section_name = "Account" + str(i) # add accounts i = 1 for acc in self: section_name = "Account" + str(i) cfg.add_section(section_name) cfg.set(section_name, 'enabled', int(acc.enabled)) cfg.set(section_name, 'name', acc.name) cfg.set(section_name, 'user', acc.user) cfg.set(section_name, 'server', acc.server) cfg.set(section_name, 'port', acc.port) cfg.set(section_name, 'ssl', int(acc.ssl)) cfg.set(section_name, 'imap', int(acc.imap)) cfg.set(section_name, 'idle', int(acc.idle)) cfg.set(section_name, 'folder', acc.folder) protocol = 'imap' if acc.imap else 'pop' self._keyring.set(protocol, acc.user, acc.server, acc.password) i = i + 1 # delete obsolete entries from Keyring self._keyring.remove(self) def _get_account_cfg(self, cfg, section_name, option_name): if cfg.has_option(section_name, option_name): return cfg.get(section_name, option_name) else: return account_defaults[option_name] Mailnag/common/__init__.py0000664000175000017500000000147012072264260015710 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # __init__.py # # Copyright 2011 Patrick Ulbrich # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # Mailnag/daemon/0000775000175000017500000000000012072264260013550 5ustar patrickpatrickMailnag/daemon/idlers.py0000664000175000017500000000263612072264260015413 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # idlers.py # # Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Leighton Earl # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # from daemon.idler import Idler class Idlers: def __init__(self, accounts, sync_callback): self._idlerlist = [] self._accounts = accounts self._sync_callback = sync_callback def run(self): for acc in self._accounts: if acc.imap and acc.idle: try: idler = Idler(acc, self._sync_callback) idler.run() self._idlerlist.append(idler) except Exception as ex: print "Error: Failed to create an idler thread for account '%s'" % acc.name def dispose(self): for idler in self._idlerlist: idler.dispose() Mailnag/daemon/reminder.py0000664000175000017500000000432212072264260015730 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # reminder.py # # Copyright 2011 Patrick Ulbrich # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # import os from common.config import cfg_folder class Reminder(dict): def load(self): # load last known messages from mailnag.dat dat_file = os.path.join(cfg_folder, 'mailnag.dat') if os.path.exists(dat_file): f = open(dat_file, 'r') # reopen file for line in f: # remove CR at the end stripedline = line.strip() # get all items from one line in a list: ["mailid", show_only_new flag"] content = stripedline.split(',') try: # add to dict [id : flag] self[content[0]] = content[1] except IndexError: # no flags in mailnag.dat self[content[0]] = '0' f.close() # save mail ids to file def save(self, mail_list): dat_file = os.path.join(cfg_folder, 'mailnag.dat') f = open(dat_file, 'w') # open for overwrite for m in mail_list: try: seen_flag = self[m.id] except KeyError: # id of a new mail is not yet known to reminder seen_flag = '0' # construct line: email_id, seen_flag line = m.id + ',' + seen_flag + '\n' f.write(line) self[m.id] = seen_flag f.close() # check if mail id is in reminder list def contains(self, id): return (id in self) # set seen flag for this email on True def set_to_seen(self, id): try: self[id] = '1' except KeyError: pass def unseen(self, id): try: flag = self[id] return (flag == '0') except KeyError: return True Mailnag/daemon/mailchecker.py0000664000175000017500000001457212072264260016402 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # mailchecker.py # # Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # from gi.repository import Notify, Gio import threading import sys import subprocess import os import time from common.utils import get_data_file, gstplay, is_online, get_default_mail_reader from common.i18n import _ from daemon.reminder import Reminder from daemon.mailsyncer import MailSyncer from daemon.pid import Pid class MailChecker: def __init__(self, cfg): self.MAIL_LIST_LIMIT = 10 # prevent flooding of the messaging tray self._firstcheck = True; # first check after startup self._mailcheck_lock = threading.Lock() self._mail_list = [] self._mailsyncer = MailSyncer(cfg) self._reminder = Reminder() self._pid = Pid() self._cfg = cfg self._gsettings = Gio.Settings.new('org.gnome.shell') # dict that tracks all notifications that need to be closed self._notifications = {} self._reminder.load() # initialize Notification Notify.init("Mailnag") def check(self, accounts): with self._mailcheck_lock: print 'Checking %s email account(s) at: %s' % (len(accounts), time.asctime()) # kill all zombies self._pid.kill() if not is_online(): print 'Error: No internet connection' return self._mail_list = self._mailsyncer.sync(accounts) unseen_mails = [] new_mails = [] script_data = "" script_data_mailcount = 0 for mail in self._mail_list: if self._reminder.contains(mail.id): # mail was fetched before if self._reminder.unseen(mail.id): # mail was not marked as seen unseen_mails.append(mail) if self._firstcheck: new_mails.append(mail) else: # mail is fetched the first time unseen_mails.append(mail) new_mails.append(mail) script_data += ' "<%s> %s"' % (mail.sender, mail.subject) script_data_mailcount += 1 script_data = str(script_data_mailcount) + script_data if len(self._mail_list) == 0: # no mails (e.g. email client has been launched) -> close notifications for n in self._notifications.itervalues(): n.close() self._notifications = {} elif len(new_mails) > 0: if self._cfg.get('general', 'notification_mode') == '1': self._notify_summary(unseen_mails) else: self._notify_single(new_mails) # play sound if it is enabled in mailnags settings and # gnome-shell notifications aren't disabled if (self._cfg.get('general', 'playsound') == '1') and \ (self._gsettings.get_int('saved-session-presence') != 2): gstplay(get_data_file(self._cfg.get('general', 'soundfile'))) self._reminder.save(self._mail_list) # process user scripts self._run_user_scripts("on_mail_check", script_data) # write stdout to log file sys.stdout.flush() self._firstcheck = False return def dispose(self): for n in self._notifications.itervalues(): n.close() def _notify_summary(self, unseen_mails): summary = "" body = "" if len(self._notifications) == 0: self._notifications['0'] = self._get_notification(" ", None, None) # empty string will emit a gtk warning ubound = len(unseen_mails) if len(unseen_mails) <= self.MAIL_LIST_LIMIT else self.MAIL_LIST_LIMIT for i in range(ubound): body += unseen_mails[i].sender + ":\n" + unseen_mails[i].subject + "\n\n" if len(unseen_mails) > self.MAIL_LIST_LIMIT: body += "" + _("(and {0} more)").format(str(len(unseen_mails) - self.MAIL_LIST_LIMIT)) + "" if len(unseen_mails) > 1: # multiple new emails summary = _("You have {0} new mails.").format(str(len(unseen_mails))) else: summary = _("You have a new mail.") self._notifications['0'].update(summary, body, "mail-unread") self._notifications['0'].show() def _notify_single(self, new_mails): for mail in new_mails: n = self._get_notification(mail.sender, mail.subject, "mail-unread") notification_id = str(id(n)) n.add_action("mark-as-read", _("Mark as read"), self._notification_action_handler, (mail, notification_id), None) n.show() self._notifications[notification_id] = n def _get_notification(self, summary, body, icon): n = Notify.Notification.new(summary, body, icon) n.set_category("email") n.add_action("default", "default", self._notification_action_handler, None, None) return n def _notification_action_handler(self, n, action, user_data): with self._mailcheck_lock: if action == "default": mailclient = get_default_mail_reader() if mailclient != None: self._pid.append(subprocess.Popen(mailclient)) # clicking the notification bubble has closed all notifications # so clear the reference array as well. self._notifications = {} elif action == "mark-as-read": self._reminder.set_to_seen(user_data[0].id) self._reminder.save(self._mail_list) # clicking the action has closed the notification # so remove its reference del self._notifications[user_data[1]] def _run_user_scripts(self, event, data): if event == "on_mail_check": if self._cfg.get('script', 'script0_enabled') == '1': script_file = self._cfg.get('script', 'script0_file') if script_file != '' and os.path.exists(script_file): self._pid.append(subprocess.Popen("%s %s" % (script_file, data), shell = True)) else: print 'Warning: cannot execute script:', script_file if (data != '0') and (self._cfg.get('script', 'script1_enabled') == '1'): script_file = self._cfg.get('script', 'script1_file') if script_file != '' and os.path.exists(script_file): self._pid.append(subprocess.Popen("%s %s" % (script_file, data), shell = True)) else: print 'Warning: cannot execute script:', script_file Mailnag/daemon/imaplib2.py0000664000175000017500000024567612072264260015645 0ustar patrickpatrick#!/usr/bin/env python """Threaded IMAP4 client. Based on RFC 3501 and original imaplib module. Public classes: IMAP4 IMAP4_SSL IMAP4_stream Public functions: Internaldate2Time ParseFlags Time2Internaldate """ __all__ = ("IMAP4", "IMAP4_SSL", "IMAP4_stream", "Internaldate2Time", "ParseFlags", "Time2Internaldate") __version__ = "2.27" __release__ = "2" __revision__ = "27" __credits__ = """ Authentication code contributed by Donn Cave June 1998. String method conversion by ESR, February 2001. GET/SETACL contributed by Anthony Baxter April 2001. IMAP4_SSL contributed by Tino Lange March 2002. GET/SETQUOTA contributed by Andreas Zeidler June 2002. PROXYAUTH contributed by Rick Holbert November 2002. IDLE via threads suggested by Philippe Normand January 2005. GET/SETANNOTATION contributed by Tomas Lindroos June 2005. COMPRESS/DEFLATE contributed by Bron Gondwana May 2009. STARTTLS from Jython's imaplib by Alan Kennedy. ID contributed by Dave Baggett November 2009. Improved untagged responses handling suggested by Dave Baggett November 2009. Improved thread naming, and 0 read detection contributed by Grant Edwards June 2010. Improved timeout handling contributed by Ivan Vovnenko October 2010. Timeout handling further improved by Ethan Glasser-Camp December 2010. Time2Internaldate() patch to match RFC2060 specification of English month names from bugs.python.org/issue11024 March 2011. starttls() bug fixed with the help of Sebastian Spaeth April 2011. Threads now set the "daemon" flag (suggested by offlineimap-project) April 2011. Single quoting introduced with the help of Vladimir Marek August 2011.""" __author__ = "Piers Lauder " __URL__ = "http://imaplib2.sourceforge.net" __license__ = "Python License" import binascii, errno, os, Queue, random, re, select, socket, sys, time, threading, zlib select_module = select # Globals CRLF = '\r\n' Debug = None # Backward compatibility IMAP4_PORT = 143 IMAP4_SSL_PORT = 993 IDLE_TIMEOUT_RESPONSE = '* IDLE TIMEOUT\r\n' IDLE_TIMEOUT = 60*29 # Don't stay in IDLE state longer READ_POLL_TIMEOUT = 30 # Without this timeout interrupted network connections can hang reader READ_SIZE = 32768 # Consume all available in socket DFLT_DEBUG_BUF_LVL = 3 # Level above which the logging output goes directly to stderr AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first # Commands CMD_VAL_STATES = 0 CMD_VAL_ASYNC = 1 NONAUTH, AUTH, SELECTED, LOGOUT = 'NONAUTH', 'AUTH', 'SELECTED', 'LOGOUT' Commands = { # name valid states asynchronous 'APPEND': ((AUTH, SELECTED), False), 'AUTHENTICATE': ((NONAUTH,), False), 'CAPABILITY': ((NONAUTH, AUTH, SELECTED), True), 'CHECK': ((SELECTED,), True), 'CLOSE': ((SELECTED,), False), 'COMPRESS': ((AUTH,), False), 'COPY': ((SELECTED,), True), 'CREATE': ((AUTH, SELECTED), True), 'DELETE': ((AUTH, SELECTED), True), 'DELETEACL': ((AUTH, SELECTED), True), 'EXAMINE': ((AUTH, SELECTED), False), 'EXPUNGE': ((SELECTED,), True), 'FETCH': ((SELECTED,), True), 'GETACL': ((AUTH, SELECTED), True), 'GETANNOTATION':((AUTH, SELECTED), True), 'GETQUOTA': ((AUTH, SELECTED), True), 'GETQUOTAROOT': ((AUTH, SELECTED), True), 'ID': ((NONAUTH, AUTH, SELECTED), True), 'IDLE': ((SELECTED,), False), 'LIST': ((AUTH, SELECTED), True), 'LOGIN': ((NONAUTH,), False), 'LOGOUT': ((NONAUTH, AUTH, LOGOUT, SELECTED), False), 'LSUB': ((AUTH, SELECTED), True), 'MYRIGHTS': ((AUTH, SELECTED), True), 'NAMESPACE': ((AUTH, SELECTED), True), 'NOOP': ((NONAUTH, AUTH, SELECTED), True), 'PARTIAL': ((SELECTED,), True), 'PROXYAUTH': ((AUTH,), False), 'RENAME': ((AUTH, SELECTED), True), 'SEARCH': ((SELECTED,), True), 'SELECT': ((AUTH, SELECTED), False), 'SETACL': ((AUTH, SELECTED), False), 'SETANNOTATION':((AUTH, SELECTED), True), 'SETQUOTA': ((AUTH, SELECTED), False), 'SORT': ((SELECTED,), True), 'STARTTLS': ((NONAUTH,), False), 'STATUS': ((AUTH, SELECTED), True), 'STORE': ((SELECTED,), True), 'SUBSCRIBE': ((AUTH, SELECTED), False), 'THREAD': ((SELECTED,), True), 'UID': ((SELECTED,), True), 'UNSUBSCRIBE': ((AUTH, SELECTED), False), } UID_direct = ('SEARCH', 'SORT', 'THREAD') def Int2AP(num): """string = Int2AP(num) Return 'num' converted to a string using characters from the set 'A'..'P' """ val, a2p = [], 'ABCDEFGHIJKLMNOP' num = int(abs(num)) while num: num, mod = divmod(num, 16) val.insert(0, a2p[mod]) return ''.join(val) class Request(object): """Private class to represent a request awaiting response.""" def __init__(self, parent, name=None, callback=None, cb_arg=None, cb_self=False): self.parent = parent self.name = name self.callback = callback # Function called to process result if not cb_self: self.callback_arg = cb_arg # Optional arg passed to "callback" else: self.callback_arg = (self, cb_arg) # Self reference required in callback arg self.tag = '%s%s' % (parent.tagpre, parent.tagnum) parent.tagnum += 1 self.ready = threading.Event() self.response = None self.aborted = None self.data = None def abort(self, typ, val): self.aborted = (typ, val) self.deliver(None) def get_response(self, exc_fmt=None): self.callback = None if __debug__: self.parent._log(3, '%s:%s.ready.wait' % (self.name, self.tag)) self.ready.wait() if self.aborted is not None: typ, val = self.aborted if exc_fmt is None: exc_fmt = '%s - %%s' % typ raise typ(exc_fmt % str(val)) return self.response def deliver(self, response): if self.callback is not None: self.callback((response, self.callback_arg, self.aborted)) return self.response = response self.ready.set() if __debug__: self.parent._log(3, '%s:%s.ready.set' % (self.name, self.tag)) class IMAP4(object): """Threaded IMAP4 client class. Instantiate with: IMAP4(host=None, port=None, debug=None, debug_file=None, identifier=None, timeout=None, debug_buf_lvl=None) host - host's name (default: localhost); port - port number (default: standard IMAP4 port); debug - debug level (default: 0 - no debug); debug_file - debug stream (default: sys.stderr); identifier - thread identifier prefix (default: host); timeout - timeout in seconds when expecting a command response (default: no timeout), debug_buf_lvl - debug level at which buffering is turned off. All IMAP4rev1 commands are supported by methods of the same name. Each command returns a tuple: (type, [data, ...]) where 'type' is usually 'OK' or 'NO', and 'data' is either the text from the tagged response, or untagged results from command. Each 'data' is either a string, or a tuple. If a tuple, then the first part is the header of the response, and the second part contains the data (ie: 'literal' value). Errors raise the exception class .error(""). IMAP4 server errors raise .abort(""), which is a sub-class of 'error'. Mailbox status changes from READ-WRITE to READ-ONLY raise the exception class .readonly(""), which is a sub-class of 'abort'. "error" exceptions imply a program error. "abort" exceptions imply the connection should be reset, and the command re-tried. "readonly" exceptions imply the command should be re-tried. All commands take two optional named arguments: 'callback' and 'cb_arg' If 'callback' is provided then the command is asynchronous, so after the command is queued for transmission, the call returns immediately with the tuple (None, None). The result will be posted by invoking "callback" with one arg, a tuple: callback((result, cb_arg, None)) or, if there was a problem: callback((None, cb_arg, (exception class, reason))) Otherwise the command is synchronous (waits for result). But note that state-changing commands will both block until previous commands have completed, and block subsequent commands until they have finished. All (non-callback) arguments to commands are converted to strings, except for AUTHENTICATE, and the last argument to APPEND which is passed as an IMAP4 literal. If necessary (the string contains any non-printing characters or white-space and isn't enclosed with either parentheses or double or single quotes) each string is quoted. However, the 'password' argument to the LOGIN command is always quoted. If you want to avoid having an argument string quoted (eg: the 'flags' argument to STORE) then enclose the string in parentheses (eg: "(\Deleted)"). If you are using "sequence sets" containing the wildcard character '*', then enclose the argument in single quotes: the quotes will be removed and the resulting string passed unquoted. Note also that you can pass in an argument with a type that doesn't evaluate to 'basestring' (eg: 'bytearray') and it will be converted to a string without quoting. There is one instance variable, 'state', that is useful for tracking whether the client needs to login to the server. If it has the value "AUTH" after instantiating the class, then the connection is pre-authenticated (otherwise it will be "NONAUTH"). Selecting a mailbox changes the state to be "SELECTED", closing a mailbox changes back to "AUTH", and once the client has logged out, the state changes to "LOGOUT" and no further commands may be issued. Note: to use this module, you must read the RFCs pertaining to the IMAP4 protocol, as the semantics of the arguments to each IMAP4 command are left to the invoker, not to mention the results. Also, most IMAP servers implement a sub-set of the commands available here. Note also that you must call logout() to shut down threads before discarding an instance. """ class error(Exception): pass # Logical errors - debug required class abort(error): pass # Service errors - close and retry class readonly(abort): pass # Mailbox status changed to READ-ONLY continuation_cre = re.compile(r'\+( (?P.*))?') literal_cre = re.compile(r'.*{(?P\d+)}$') mapCRLF_cre = re.compile(r'\r\n|\r|\n') # Need to quote "atom-specials" :- # "(" / ")" / "{" / SP / 0x00 - 0x1f / 0x7f / "%" / "*" / DQUOTE / "\" / "]" # so match not the inverse set mustquote_cre = re.compile(r"[^!#$&'+,./0-9:;<=>?@A-Z\[^_`a-z|}~-]") response_code_cre = re.compile(r'\[(?P[A-Z-]+)( (?P[^\]]*))?\]') # sequence_set_cre = re.compile(r"^[0-9]+(:([0-9]+|\*))?(,[0-9]+(:([0-9]+|\*))?)*$") untagged_response_cre = re.compile(r'\* (?P[A-Z-]+)( (?P.*))?') untagged_status_cre = re.compile(r'\* (?P\d+) (?P[A-Z-]+)( (?P.*))?') def __init__(self, host=None, port=None, debug=None, debug_file=None, identifier=None, timeout=None, debug_buf_lvl=None): self.state = NONAUTH # IMAP4 protocol state self.literal = None # A literal argument to a command self.tagged_commands = {} # Tagged commands awaiting response self.untagged_responses = [] # [[typ: [data, ...]], ...] self.mailbox = None # Current mailbox selected self.mailboxes = {} # Untagged responses state per mailbox self.is_readonly = False # READ-ONLY desired state self.idle_rqb = None # Server IDLE Request - see _IdleCont self.idle_timeout = None # Must prod server occasionally self._expecting_data = 0 # Expecting message data self._accumulated_data = [] # Message data accumulated so far self._literal_expected = None # Message data descriptor self.compressor = None # COMPRESS/DEFLATE if not None self.decompressor = None # Create unique tag for this session, # and compile tagged response matcher. self.tagnum = 0 self.tagpre = Int2AP(random.randint(4096, 65535)) self.tagre = re.compile(r'(?P' + self.tagpre + r'\d+) (?P[A-Z]+) (?P.*)') if __debug__: self._init_debug(debug, debug_file, debug_buf_lvl) self.resp_timeout = timeout # Timeout waiting for command response if timeout is not None and timeout < READ_POLL_TIMEOUT: self.read_poll_timeout = timeout else: self.read_poll_timeout = READ_POLL_TIMEOUT self.read_size = READ_SIZE # Open socket to server. self.open(host, port) if __debug__: if debug: self._mesg('connected to %s on port %s' % (self.host, self.port)) # Threading if identifier is not None: self.identifier = identifier else: self.identifier = self.host if self.identifier: self.identifier += ' ' self.Terminate = self.TerminateReader = False self.state_change_free = threading.Event() self.state_change_pending = threading.Lock() self.commands_lock = threading.Lock() self.idle_lock = threading.Lock() self.ouq = Queue.Queue(10) self.inq = Queue.Queue() self.wrth = threading.Thread(target=self._writer) self.wrth.setDaemon(True) self.wrth.start() self.rdth = threading.Thread(target=self._reader) self.rdth.setDaemon(True) self.rdth.start() self.inth = threading.Thread(target=self._handler) self.inth.setDaemon(True) self.inth.start() # Get server welcome message, # request and store CAPABILITY response. try: self.welcome = self._request_push(tag='continuation').get_response('IMAP4 protocol error: %s')[1] if self._get_untagged_response('PREAUTH'): self.state = AUTH if __debug__: self._log(1, 'state => AUTH') elif self._get_untagged_response('OK'): if __debug__: self._log(1, 'state => NONAUTH') else: raise self.error('unrecognised server welcome message: %s' % `self.welcome`) typ, dat = self.capability() if dat == [None]: raise self.error('no CAPABILITY response from server') self.capabilities = tuple(dat[-1].upper().split()) if __debug__: self._log(1, 'CAPABILITY: %r' % (self.capabilities,)) for version in AllowedVersions: if not version in self.capabilities: continue self.PROTOCOL_VERSION = version break else: raise self.error('server not IMAP4 compliant') except: self._close_threads() raise def __getattr__(self, attr): # Allow UPPERCASE variants of IMAP4 command methods. if attr in Commands: return getattr(self, attr.lower()) raise AttributeError("Unknown IMAP4 command: '%s'" % attr) # Overridable methods def open(self, host=None, port=None): """open(host=None, port=None) Setup connection to remote server on "host:port" (default: localhost:standard IMAP4 port). This connection will be used by the routines: read, send, shutdown, socket.""" self.host = self._choose_nonull_or_dflt('', host) self.port = self._choose_nonull_or_dflt(IMAP4_PORT, port) self.sock = self.open_socket() self.read_fd = self.sock.fileno() def open_socket(self): """open_socket() Open socket choosing first address family available.""" msg = (-1, 'could not open socket') for res in socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res try: s = socket.socket(af, socktype, proto) except socket.error, msg: continue try: for i in (0, 1): try: s.connect(sa) break except socket.error, msg: if len(msg.args) < 2 or msg.args[0] != errno.EINTR: raise else: raise socket.error(msg) except socket.error, msg: s.close() continue break else: raise socket.error(msg) return s def ssl_wrap_socket(self): # Allow sending of keep-alive messages - seems to prevent some servers # from closing SSL, leading to deadlocks. self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) try: import ssl if self.ca_certs is not None: cert_reqs = ssl.CERT_REQUIRED else: cert_reqs = ssl.CERT_NONE self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile, ca_certs=self.ca_certs, cert_reqs=cert_reqs) ssl_exc = ssl.SSLError except ImportError: # No ssl module, and socket.ssl does not allow certificate verification if self.ca_certs is not None: raise socket.sslerror("SSL CA certificates cannot be checked without ssl module") self.sock = socket.ssl(self.sock, self.keyfile, self.certfile) ssl_exc = socket.sslerror if self.cert_verify_cb is not None: cert_err = self.cert_verify_cb(self.sock.getpeercert(), self.host) if cert__err: raise ssl_exc(cert_err) self.read_fd = self.sock.fileno() def start_compressing(self): """start_compressing() Enable deflate compression on the socket (RFC 4978).""" # rfc 1951 - pure DEFLATE, so use -15 for both windows self.decompressor = zlib.decompressobj(-15) self.compressor = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) def read(self, size): """data = read(size) Read at most 'size' bytes from remote.""" if self.decompressor is None: return self.sock.recv(size) if self.decompressor.unconsumed_tail: data = self.decompressor.unconsumed_tail else: data = self.sock.recv(8192) return self.decompressor.decompress(data, size) def send(self, data): """send(data) Send 'data' to remote.""" if self.compressor is not None: data = self.compressor.compress(data) data += self.compressor.flush(zlib.Z_SYNC_FLUSH) self.sock.sendall(data) def shutdown(self): """shutdown() Close I/O established in "open".""" self.sock.close() def socket(self): """socket = socket() Return socket instance used to connect to IMAP4 server.""" return self.sock # Utility methods def enable_compression(self): """enable_compression() Ask the server to start compressing the connection. Should be called from user of this class after instantiation, as in: if 'COMPRESS=DEFLATE' in imapobj.capabilities: imapobj.enable_compression()""" try: typ, dat = self._simple_command('COMPRESS', 'DEFLATE') if typ == 'OK': self.start_compressing() if __debug__: self._log(1, 'Enabled COMPRESS=DEFLATE') finally: self._release_state_change() def pop_untagged_responses(self): """ for typ,data in pop_untagged_responses(): pass Generator for any remaining untagged responses. Returns and removes untagged responses in order of reception. Use at your own risk!""" while self.untagged_responses: self.commands_lock.acquire() try: yield self.untagged_responses.pop(0) finally: self.commands_lock.release() def recent(self, **kw): """(typ, [data]) = recent() Return 'RECENT' responses if any exist, else prompt server for an update using the 'NOOP' command. 'data' is None if no new messages, else list of RECENT responses, most recent last.""" name = 'RECENT' typ, dat = self._untagged_response(None, [None], name) if dat != [None]: return self._deliver_dat(typ, dat, kw) kw['untagged_response'] = name return self.noop(**kw) # Prod server for response def response(self, code, **kw): """(code, [data]) = response(code) Return data for response 'code' if received, or None. Old value for response 'code' is cleared.""" typ, dat = self._untagged_response(code, [None], code.upper()) return self._deliver_dat(typ, dat, kw) # IMAP4 commands def append(self, mailbox, flags, date_time, message, **kw): """(typ, [data]) = append(mailbox, flags, date_time, message) Append message to named mailbox. All args except `message' can be None.""" name = 'APPEND' if not mailbox: mailbox = 'INBOX' if flags: if (flags[0],flags[-1]) != ('(',')'): flags = '(%s)' % flags else: flags = None if date_time: date_time = Time2Internaldate(date_time) else: date_time = None self.literal = self.mapCRLF_cre.sub(CRLF, message) try: return self._simple_command(name, mailbox, flags, date_time, **kw) finally: self._release_state_change() def authenticate(self, mechanism, authobject, **kw): """(typ, [data]) = authenticate(mechanism, authobject) Authenticate command - requires response processing. 'mechanism' specifies which authentication mechanism is to be used - it must appear in .capabilities in the form AUTH=. 'authobject' must be a callable object: data = authobject(response) It will be called to process server continuation responses. It should return data that will be encoded and sent to server. It should return None if the client abort response '*' should be sent instead.""" self.literal = _Authenticator(authobject).process try: typ, dat = self._simple_command('AUTHENTICATE', mechanism.upper()) if typ != 'OK': self._deliver_exc(self.error, dat[-1], kw) self.state = AUTH if __debug__: self._log(1, 'state => AUTH') finally: self._release_state_change() return self._deliver_dat(typ, dat, kw) def capability(self, **kw): """(typ, [data]) = capability() Fetch capabilities list from server.""" name = 'CAPABILITY' kw['untagged_response'] = name return self._simple_command(name, **kw) def check(self, **kw): """(typ, [data]) = check() Checkpoint mailbox on server.""" return self._simple_command('CHECK', **kw) def close(self, **kw): """(typ, [data]) = close() Close currently selected mailbox. Deleted messages are removed from writable mailbox. This is the recommended command before 'LOGOUT'.""" if self.state != 'SELECTED': raise self.error('No mailbox selected.') try: typ, dat = self._simple_command('CLOSE') finally: self.state = AUTH if __debug__: self._log(1, 'state => AUTH') self._release_state_change() return self._deliver_dat(typ, dat, kw) def copy(self, message_set, new_mailbox, **kw): """(typ, [data]) = copy(message_set, new_mailbox) Copy 'message_set' messages onto end of 'new_mailbox'.""" return self._simple_command('COPY', message_set, new_mailbox, **kw) def create(self, mailbox, **kw): """(typ, [data]) = create(mailbox) Create new mailbox.""" return self._simple_command('CREATE', mailbox, **kw) def delete(self, mailbox, **kw): """(typ, [data]) = delete(mailbox) Delete old mailbox.""" return self._simple_command('DELETE', mailbox, **kw) def deleteacl(self, mailbox, who, **kw): """(typ, [data]) = deleteacl(mailbox, who) Delete the ACLs (remove any rights) set for who on mailbox.""" return self._simple_command('DELETEACL', mailbox, who, **kw) def examine(self, mailbox='INBOX', **kw): """(typ, [data]) = examine(mailbox='INBOX', readonly=False) Select a mailbox for READ-ONLY access. (Flushes all untagged responses.) 'data' is count of messages in mailbox ('EXISTS' response). Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so other responses should be obtained via "response('FLAGS')" etc.""" return self.select(mailbox=mailbox, readonly=True, **kw) def expunge(self, **kw): """(typ, [data]) = expunge() Permanently remove deleted items from selected mailbox. Generates 'EXPUNGE' response for each deleted message. 'data' is list of 'EXPUNGE'd message numbers in order received.""" name = 'EXPUNGE' kw['untagged_response'] = name return self._simple_command(name, **kw) def fetch(self, message_set, message_parts, **kw): """(typ, [data, ...]) = fetch(message_set, message_parts) Fetch (parts of) messages. 'message_parts' should be a string of selected parts enclosed in parentheses, eg: "(UID BODY[TEXT])". 'data' are tuples of message part envelope and data, followed by a string containing the trailer.""" name = 'FETCH' kw['untagged_response'] = name return self._simple_command(name, message_set, message_parts, **kw) def getacl(self, mailbox, **kw): """(typ, [data]) = getacl(mailbox) Get the ACLs for a mailbox.""" kw['untagged_response'] = 'ACL' return self._simple_command('GETACL', mailbox, **kw) def getannotation(self, mailbox, entry, attribute, **kw): """(typ, [data]) = getannotation(mailbox, entry, attribute) Retrieve ANNOTATIONs.""" kw['untagged_response'] = 'ANNOTATION' return self._simple_command('GETANNOTATION', mailbox, entry, attribute, **kw) def getquota(self, root, **kw): """(typ, [data]) = getquota(root) Get the quota root's resource usage and limits. (Part of the IMAP4 QUOTA extension defined in rfc2087.)""" kw['untagged_response'] = 'QUOTA' return self._simple_command('GETQUOTA', root, **kw) def getquotaroot(self, mailbox, **kw): # Hmmm, this is non-std! Left for backwards-compatibility, sigh. # NB: usage should have been defined as: # (typ, [QUOTAROOT responses...]) = getquotaroot(mailbox) # (typ, [QUOTA responses...]) = response('QUOTA') """(typ, [[QUOTAROOT responses...], [QUOTA responses...]]) = getquotaroot(mailbox) Get the list of quota roots for the named mailbox.""" typ, dat = self._simple_command('GETQUOTAROOT', mailbox) typ, quota = self._untagged_response(typ, dat, 'QUOTA') typ, quotaroot = self._untagged_response(typ, dat, 'QUOTAROOT') return self._deliver_dat(typ, [quotaroot, quota], kw) def id(self, *kv_pairs, **kw): """(typ, [data]) = .id(kv_pairs) 'data' is list of ID key value pairs. Request information for problem analysis and determination. The ID extension is defined in RFC 2971. """ name = 'ID' kw['untagged_response'] = name return self._simple_command(name, *kv_pairs, **kw) def idle(self, timeout=None, **kw): """"(typ, [data]) = idle(timeout=None) Put server into IDLE mode until server notifies some change, or 'timeout' (secs) occurs (default: 29 minutes), or another IMAP4 command is scheduled.""" name = 'IDLE' self.literal = _IdleCont(self, timeout).process try: return self._simple_command(name, **kw) finally: self._release_state_change() def list(self, directory='""', pattern='*', **kw): """(typ, [data]) = list(directory='""', pattern='*') List mailbox names in directory matching pattern. 'data' is list of LIST responses. NB: for 'pattern': % matches all except separator ( so LIST "" "%" returns names at root) * matches all (so LIST "" "*" returns whole directory tree from root)""" name = 'LIST' kw['untagged_response'] = name return self._simple_command(name, directory, pattern, **kw) def login(self, user, password, **kw): """(typ, [data]) = login(user, password) Identify client using plaintext password. NB: 'password' will be quoted.""" try: typ, dat = self._simple_command('LOGIN', user, self._quote(password)) if typ != 'OK': self._deliver_exc(self.error, dat[-1], kw) self.state = AUTH if __debug__: self._log(1, 'state => AUTH') finally: self._release_state_change() return self._deliver_dat(typ, dat, kw) def login_cram_md5(self, user, password, **kw): """(typ, [data]) = login_cram_md5(user, password) Force use of CRAM-MD5 authentication.""" self.user, self.password = user, password return self.authenticate('CRAM-MD5', self._CRAM_MD5_AUTH, **kw) def _CRAM_MD5_AUTH(self, challenge): """Authobject to use with CRAM-MD5 authentication.""" import hmac return self.user + " " + hmac.HMAC(self.password, challenge).hexdigest() def logout(self, **kw): """(typ, [data]) = logout() Shutdown connection to server. Returns server 'BYE' response. NB: You must call this to shut down threads before discarding an instance.""" self.state = LOGOUT if __debug__: self._log(1, 'state => LOGOUT') try: try: typ, dat = self._simple_command('LOGOUT') except: typ, dat = 'NO', ['%s: %s' % sys.exc_info()[:2]] if __debug__: self._log(1, dat) self._close_threads() finally: self._release_state_change() if __debug__: self._log(1, 'connection closed') bye = self._get_untagged_response('BYE', leave=True) if bye: typ, dat = 'BYE', bye return self._deliver_dat(typ, dat, kw) def lsub(self, directory='""', pattern='*', **kw): """(typ, [data, ...]) = lsub(directory='""', pattern='*') List 'subscribed' mailbox names in directory matching pattern. 'data' are tuples of message part envelope and data.""" name = 'LSUB' kw['untagged_response'] = name return self._simple_command(name, directory, pattern, **kw) def myrights(self, mailbox, **kw): """(typ, [data]) = myrights(mailbox) Show my ACLs for a mailbox (i.e. the rights that I have on mailbox).""" name = 'MYRIGHTS' kw['untagged_response'] = name return self._simple_command(name, mailbox, **kw) def namespace(self, **kw): """(typ, [data, ...]) = namespace() Returns IMAP namespaces ala rfc2342.""" name = 'NAMESPACE' kw['untagged_response'] = name return self._simple_command(name, **kw) def noop(self, **kw): """(typ, [data]) = noop() Send NOOP command.""" if __debug__: self._dump_ur(3) return self._simple_command('NOOP', **kw) def partial(self, message_num, message_part, start, length, **kw): """(typ, [data, ...]) = partial(message_num, message_part, start, length) Fetch truncated part of a message. 'data' is tuple of message part envelope and data. NB: obsolete.""" name = 'PARTIAL' kw['untagged_response'] = 'FETCH' return self._simple_command(name, message_num, message_part, start, length, **kw) def proxyauth(self, user, **kw): """(typ, [data]) = proxyauth(user) Assume authentication as 'user'. (Allows an authorised administrator to proxy into any user's mailbox.)""" try: return self._simple_command('PROXYAUTH', user, **kw) finally: self._release_state_change() def rename(self, oldmailbox, newmailbox, **kw): """(typ, [data]) = rename(oldmailbox, newmailbox) Rename old mailbox name to new.""" return self._simple_command('RENAME', oldmailbox, newmailbox, **kw) def search(self, charset, *criteria, **kw): """(typ, [data]) = search(charset, criterion, ...) Search mailbox for matching messages. 'data' is space separated list of matching message numbers.""" name = 'SEARCH' kw['untagged_response'] = name if charset: return self._simple_command(name, 'CHARSET', charset, *criteria, **kw) return self._simple_command(name, *criteria, **kw) def select(self, mailbox='INBOX', readonly=False, **kw): """(typ, [data]) = select(mailbox='INBOX', readonly=False) Select a mailbox. (Restores any previous untagged responses.) 'data' is count of messages in mailbox ('EXISTS' response). Mandated responses are ('FLAGS', 'EXISTS', 'RECENT', 'UIDVALIDITY'), so other responses should be obtained via "response('FLAGS')" etc.""" self.commands_lock.acquire() # Save state of old mailbox, restore state for new... self.mailboxes[self.mailbox] = self.untagged_responses self.untagged_responses = self.mailboxes.setdefault(mailbox, []) self.commands_lock.release() self.mailbox = mailbox self.is_readonly = readonly and True or False if readonly: name = 'EXAMINE' else: name = 'SELECT' try: rqb = self._command(name, mailbox) typ, dat = rqb.get_response('command: %s => %%s' % rqb.name) if typ != 'OK': if self.state == SELECTED: self.state = AUTH if __debug__: self._log(1, 'state => AUTH') if typ == 'BAD': self._deliver_exc(self.error, '%s command error: %s %s. Data: %.100s' % (name, typ, dat, mailbox), kw) return self._deliver_dat(typ, dat, kw) self.state = SELECTED if __debug__: self._log(1, 'state => SELECTED') finally: self._release_state_change() if self._get_untagged_response('READ-ONLY', leave=True) and not readonly: if __debug__: self._dump_ur(1) self._deliver_exc(self.readonly, '%s is not writable' % mailbox, kw) typ, dat = self._untagged_response(typ, [None], 'EXISTS') return self._deliver_dat(typ, dat, kw) def setacl(self, mailbox, who, what, **kw): """(typ, [data]) = setacl(mailbox, who, what) Set a mailbox acl.""" try: return self._simple_command('SETACL', mailbox, who, what, **kw) finally: self._release_state_change() def setannotation(self, *args, **kw): """(typ, [data]) = setannotation(mailbox[, entry, attribute]+) Set ANNOTATIONs.""" kw['untagged_response'] = 'ANNOTATION' return self._simple_command('SETANNOTATION', *args, **kw) def setquota(self, root, limits, **kw): """(typ, [data]) = setquota(root, limits) Set the quota root's resource limits.""" kw['untagged_response'] = 'QUOTA' try: return self._simple_command('SETQUOTA', root, limits, **kw) finally: self._release_state_change() def sort(self, sort_criteria, charset, *search_criteria, **kw): """(typ, [data]) = sort(sort_criteria, charset, search_criteria, ...) IMAP4rev1 extension SORT command.""" name = 'SORT' if (sort_criteria[0],sort_criteria[-1]) != ('(',')'): sort_criteria = '(%s)' % sort_criteria kw['untagged_response'] = name return self._simple_command(name, sort_criteria, charset, *search_criteria, **kw) def starttls(self, keyfile=None, certfile=None, ca_certs=None, cert_verify_cb=None, **kw): """(typ, [data]) = starttls(keyfile=None, certfile=None, ca_certs=None, cert_verify_cb=None) Start TLS negotiation as per RFC 2595.""" name = 'STARTTLS' if name not in self.capabilities: raise self.abort('TLS not supported by server') if hasattr(self, '_tls_established') and self._tls_established: raise self.abort('TLS session already established') # Must now shutdown reader thread after next response, and restart after changing read_fd self.read_size = 1 # Don't consume TLS handshake self.TerminateReader = True try: typ, dat = self._simple_command(name) finally: self._release_state_change() self.rdth.join() self.TerminateReader = False self.read_size = READ_SIZE if typ != 'OK': # Restart reader thread and error self.rdth = threading.Thread(target=self._reader) self.rdth.setDaemon(True) self.rdth.start() raise self.error("Couldn't establish TLS session: %s" % dat) self.keyfile = keyfile self.certfile = certfile self.ca_certs = ca_certs self.cert_verify_cb = cert_verify_cb try: self.ssl_wrap_socket() finally: # Restart reader thread self.rdth = threading.Thread(target=self._reader) self.rdth.setDaemon(True) self.rdth.start() typ, dat = self.capability() if dat == [None]: raise self.error('no CAPABILITY response from server') self.capabilities = tuple(dat[-1].upper().split()) self._tls_established = True typ, dat = self._untagged_response(typ, dat, name) return self._deliver_dat(typ, dat, kw) def status(self, mailbox, names, **kw): """(typ, [data]) = status(mailbox, names) Request named status conditions for mailbox.""" name = 'STATUS' kw['untagged_response'] = name return self._simple_command(name, mailbox, names, **kw) def store(self, message_set, command, flags, **kw): """(typ, [data]) = store(message_set, command, flags) Alters flag dispositions for messages in mailbox.""" if (flags[0],flags[-1]) != ('(',')'): flags = '(%s)' % flags # Avoid quoting the flags kw['untagged_response'] = 'FETCH' return self._simple_command('STORE', message_set, command, flags, **kw) def subscribe(self, mailbox, **kw): """(typ, [data]) = subscribe(mailbox) Subscribe to new mailbox.""" try: return self._simple_command('SUBSCRIBE', mailbox, **kw) finally: self._release_state_change() def thread(self, threading_algorithm, charset, *search_criteria, **kw): """(type, [data]) = thread(threading_alogrithm, charset, search_criteria, ...) IMAPrev1 extension THREAD command.""" name = 'THREAD' kw['untagged_response'] = name return self._simple_command(name, threading_algorithm, charset, *search_criteria, **kw) def uid(self, command, *args, **kw): """(typ, [data]) = uid(command, arg, ...) Execute "command arg ..." with messages identified by UID, rather than message number. Assumes 'command' is legal in current state. Returns response appropriate to 'command'.""" command = command.upper() if command in UID_direct: resp = command else: resp = 'FETCH' kw['untagged_response'] = resp return self._simple_command('UID', command, *args, **kw) def unsubscribe(self, mailbox, **kw): """(typ, [data]) = unsubscribe(mailbox) Unsubscribe from old mailbox.""" try: return self._simple_command('UNSUBSCRIBE', mailbox, **kw) finally: self._release_state_change() def xatom(self, name, *args, **kw): """(typ, [data]) = xatom(name, arg, ...) Allow simple extension commands notified by server in CAPABILITY response. Assumes extension command 'name' is legal in current state. Returns response appropriate to extension command 'name'.""" name = name.upper() if not name in Commands: Commands[name] = ((self.state,), False) try: return self._simple_command(name, *args, **kw) finally: self._release_state_change() # Internal methods def _append_untagged(self, typ, dat): # Append new 'dat' to end of last untagged response if same 'typ', # else append new response. if dat is None: dat = '' self.commands_lock.acquire() if self.untagged_responses: urn, urd = self.untagged_responses[-1] if urn != typ: urd = None else: urd = None if urd is None: urd = [] self.untagged_responses.append([typ, urd]) urd.append(dat) self.commands_lock.release() if __debug__: self._log(5, 'untagged_responses[%s] %s += ["%s"]' % (typ, len(urd)-1, dat)) def _check_bye(self): bye = self._get_untagged_response('BYE', leave=True) if bye: raise self.abort(bye[-1]) def _checkquote(self, arg): # Must quote command args if "atom-specials" present, # and not already quoted. NB: single quotes are removed. if not isinstance(arg, basestring): return arg if len(arg) >= 2 and (arg[0],arg[-1]) in (('(',')'),('"','"')): return arg if len(arg) >= 2 and (arg[0],arg[-1]) in (("'","'"),): return arg[1:-1] if arg and self.mustquote_cre.search(arg) is None: return arg return self._quote(arg) def _choose_nonull_or_dflt(self, dflt, *args): dflttyp = type(dflt) for arg in args: if arg is not None: if type(arg) is dflttyp: return arg if __debug__: self._log(1, 'bad arg type is %s, expecting %s' % (type(arg), dflttyp)) return dflt def _command(self, name, *args, **kw): if Commands[name][CMD_VAL_ASYNC]: cmdtyp = 'async' else: cmdtyp = 'sync' if __debug__: self._log(1, '[%s] %s %s' % (cmdtyp, name, args)) if __debug__: self._log(3, 'state_change_pending.acquire') self.state_change_pending.acquire() self._end_idle() if cmdtyp == 'async': self.state_change_pending.release() if __debug__: self._log(3, 'state_change_pending.release') else: # Need to wait for all async commands to complete self._check_bye() self.commands_lock.acquire() if self.tagged_commands: self.state_change_free.clear() need_event = True else: need_event = False self.commands_lock.release() if need_event: if __debug__: self._log(3, 'sync command %s waiting for empty commands Q' % name) self.state_change_free.wait() if __debug__: self._log(3, 'sync command %s proceeding' % name) if self.state not in Commands[name][CMD_VAL_STATES]: self.literal = None raise self.error('command %s illegal in state %s' % (name, self.state)) self._check_bye() for typ in ('OK', 'NO', 'BAD'): self._get_untagged_response(typ) if self._get_untagged_response('READ-ONLY', leave=True) and not self.is_readonly: self.literal = None raise self.readonly('mailbox status changed to READ-ONLY') if self.Terminate: raise self.abort('connection closed') rqb = self._request_push(name=name, **kw) data = '%s %s' % (rqb.tag, name) for arg in args: if arg is None: continue data = '%s %s' % (data, self._checkquote(arg)) literal = self.literal if literal is not None: self.literal = None if isinstance(literal, basestring): literator = None data = '%s {%s}' % (data, len(literal)) else: literator = literal if __debug__: self._log(4, 'data=%s' % data) rqb.data = '%s%s' % (data, CRLF) if literal is None: self.ouq.put(rqb) return rqb # Must setup continuation expectancy *before* ouq.put crqb = self._request_push(tag='continuation') self.ouq.put(rqb) while True: # Wait for continuation response ok, data = crqb.get_response('command: %s => %%s' % name) if __debug__: self._log(4, 'continuation => %s, %s' % (ok, data)) # NO/BAD response? if not ok: break # Send literal if literator is not None: literal = literator(data, rqb) if literal is None: break if literator is not None: # Need new request for next continuation response crqb = self._request_push(tag='continuation') if __debug__: self._log(4, 'write literal size %s' % len(literal)) crqb.data = '%s%s' % (literal, CRLF) self.ouq.put(crqb) if literator is None: break return rqb def _command_complete(self, rqb, kw): # Called for non-callback commands typ, dat = rqb.get_response('command: %s => %%s' % rqb.name) self._check_bye() if typ == 'BAD': if __debug__: self._print_log() raise self.error('%s command error: %s %s. Data: %.100s' % (rqb.name, typ, dat, rqb.data)) if 'untagged_response' in kw: return self._untagged_response(typ, dat, kw['untagged_response']) return typ, dat def _command_completer(self, (response, cb_arg, error)): # Called for callback commands rqb, kw = cb_arg rqb.callback = kw['callback'] rqb.callback_arg = kw.get('cb_arg') if error is not None: if __debug__: self._print_log() typ, val = error rqb.abort(typ, val) return bye = self._get_untagged_response('BYE', leave=True) if bye: rqb.abort(self.abort, bye[-1]) return typ, dat = response if typ == 'BAD': if __debug__: self._print_log() rqb.abort(self.error, '%s command error: %s %s. Data: %.100s' % (rqb.name, typ, dat, rqb.data)) return if 'untagged_response' in kw: response = self._untagged_response(typ, dat, kw['untagged_response']) rqb.deliver(response) def _deliver_dat(self, typ, dat, kw): if 'callback' in kw: kw['callback'](((typ, dat), kw.get('cb_arg'), None)) return typ, dat def _deliver_exc(self, exc, dat, kw): if 'callback' in kw: kw['callback']((None, kw.get('cb_arg'), (exc, dat))) raise exc(dat) def _end_idle(self): self.idle_lock.acquire() irqb = self.idle_rqb if irqb is None: self.idle_lock.release() return self.idle_rqb = None self.idle_timeout = None self.idle_lock.release() irqb.data = 'DONE%s' % CRLF self.ouq.put(irqb) if __debug__: self._log(2, 'server IDLE finished') def _get_untagged_response(self, name, leave=False): self.commands_lock.acquire() for i, (typ, dat) in enumerate(self.untagged_responses): if typ == name: if not leave: del self.untagged_responses[i] self.commands_lock.release() if __debug__: self._log(5, '_get_untagged_response(%s) => %s' % (name, dat)) return dat self.commands_lock.release() return None def _match(self, cre, s): # Run compiled regular expression 'cre' match method on 's'. # Save result, return success. self.mo = cre.match(s) return self.mo is not None def _put_response(self, resp): if self._expecting_data > 0: rlen = len(resp) dlen = min(self._expecting_data, rlen) self._expecting_data -= dlen if rlen <= dlen: self._accumulated_data.append(resp) return self._accumulated_data.append(resp[:dlen]) resp = resp[dlen:] if self._accumulated_data: typ, dat = self._literal_expected self._append_untagged(typ, (dat, ''.join(self._accumulated_data))) self._accumulated_data = [] # Protocol mandates all lines terminated by CRLF resp = resp[:-2] if 'continuation' in self.tagged_commands: continuation_expected = True else: continuation_expected = False if self._literal_expected is not None: dat = resp if self._match(self.literal_cre, dat): self._literal_expected[1] = dat self._expecting_data = int(self.mo.group('size')) if __debug__: self._log(4, 'expecting literal size %s' % self._expecting_data) return typ = self._literal_expected[0] self._literal_expected = None self._append_untagged(typ, dat) # Tail if __debug__: self._log(4, 'literal completed') else: # Command completion response? if self._match(self.tagre, resp): tag = self.mo.group('tag') typ = self.mo.group('type') dat = self.mo.group('data') if not tag in self.tagged_commands: if __debug__: self._log(1, 'unexpected tagged response: %s' % resp) else: self._request_pop(tag, (typ, [dat])) else: dat2 = None # '*' (untagged) responses? if not self._match(self.untagged_response_cre, resp): if self._match(self.untagged_status_cre, resp): dat2 = self.mo.group('data2') if self.mo is None: # Only other possibility is '+' (continuation) response... if self._match(self.continuation_cre, resp): if not continuation_expected: if __debug__: self._log(1, "unexpected continuation response: '%s'" % resp) return self._request_pop('continuation', (True, self.mo.group('data'))) return if __debug__: self._log(1, "unexpected response: '%s'" % resp) return typ = self.mo.group('type') dat = self.mo.group('data') if dat is None: dat = '' # Null untagged response if dat2: dat = dat + ' ' + dat2 # Is there a literal to come? if self._match(self.literal_cre, dat): self._expecting_data = int(self.mo.group('size')) if __debug__: self._log(4, 'read literal size %s' % self._expecting_data) self._literal_expected = [typ, dat] return self._append_untagged(typ, dat) if typ != 'OK': # NO, BYE, IDLE self._end_idle() # Bracketed response information? if typ in ('OK', 'NO', 'BAD') and self._match(self.response_code_cre, dat): self._append_untagged(self.mo.group('type'), self.mo.group('data')) # Command waiting for aborted continuation response? if continuation_expected: self._request_pop('continuation', (False, resp)) # Bad news? if typ in ('NO', 'BAD', 'BYE'): if typ == 'BYE': self.Terminate = True if __debug__: self._log(1, '%s response: %s' % (typ, dat)) def _quote(self, arg): return '"%s"' % arg.replace('\\', '\\\\').replace('"', '\\"') def _release_state_change(self): if self.state_change_pending.locked(): self.state_change_pending.release() if __debug__: self._log(3, 'state_change_pending.release') def _request_pop(self, name, data): self.commands_lock.acquire() rqb = self.tagged_commands.pop(name) if not self.tagged_commands: if __debug__: self._log(3, 'state_change_free.set') self.state_change_free.set() self.commands_lock.release() if __debug__: self._log(4, '_request_pop(%s, %s) = %s' % (name, data, rqb.tag)) rqb.deliver(data) def _request_push(self, tag=None, name=None, **kw): self.commands_lock.acquire() rqb = Request(self, name=name, **kw) if tag is None: tag = rqb.tag self.tagged_commands[tag] = rqb self.commands_lock.release() if __debug__: self._log(4, '_request_push(%s, %s, %s) = %s' % (tag, name, `kw`, rqb.tag)) return rqb def _simple_command(self, name, *args, **kw): if 'callback' in kw: self._command(name, *args, callback=self._command_completer, cb_arg=kw, cb_self=True) return (None, None) return self._command_complete(self._command(name, *args), kw) def _untagged_response(self, typ, dat, name): if typ == 'NO': return typ, dat data = self._get_untagged_response(name) if not data: return typ, [None] while True: dat = self._get_untagged_response(name) if not dat: break data += dat if __debug__: self._log(4, '_untagged_response(%s, ?, %s) => %s' % (typ, name, data)) return typ, data # Threads def _close_threads(self): if __debug__: self._log(1, '_close_threads') self.ouq.put(None) self.wrth.join() if __debug__: self._log(1, 'call shutdown') self.shutdown() self.rdth.join() self.inth.join() def _handler(self): resp_timeout = self.resp_timeout threading.currentThread().setName(self.identifier + 'handler') time.sleep(0.1) # Don't start handling before main thread ready if __debug__: self._log(1, 'starting') typ, val = self.abort, 'connection terminated' while not self.Terminate: try: if self.idle_timeout is not None: timeout = self.idle_timeout - time.time() if timeout <= 0: timeout = 1 if __debug__: if self.idle_rqb is not None: self._log(5, 'server IDLING, timeout=%.2f' % timeout) else: timeout = resp_timeout line = self.inq.get(True, timeout) except Queue.Empty: if self.idle_rqb is None: if resp_timeout is not None and self.tagged_commands: if __debug__: self._log(1, 'response timeout') typ, val = self.abort, 'no response after %s secs' % resp_timeout break continue if self.idle_timeout > time.time(): continue if __debug__: self._log(2, 'server IDLE timedout') line = IDLE_TIMEOUT_RESPONSE if line is None: if __debug__: self._log(1, 'inq None - terminating') break if not isinstance(line, basestring): typ, val = line break try: self._put_response(line) except: typ, val = self.error, 'program error: %s - %s' % sys.exc_info()[:2] break self.Terminate = True if __debug__: self._log(1, 'terminating: %s' % `val`) while not self.ouq.empty(): try: self.ouq.get_nowait().abort(typ, val) except Queue.Empty: break self.ouq.put(None) self.commands_lock.acquire() for name in self.tagged_commands.keys(): rqb = self.tagged_commands.pop(name) rqb.abort(typ, val) self.state_change_free.set() self.commands_lock.release() if __debug__: self._log(3, 'state_change_free.set') if __debug__: self._log(1, 'finished') if hasattr(select_module, "poll"): def _reader(self): threading.currentThread().setName(self.identifier + 'reader') if __debug__: self._log(1, 'starting using poll') def poll_error(state): PollErrors = { select.POLLERR: 'Error', select.POLLHUP: 'Hang up', select.POLLNVAL: 'Invalid request: descriptor not open', } return ' '.join([PollErrors[s] for s in PollErrors.keys() if (s & state)]) line_part = '' poll = select.poll() poll.register(self.read_fd, select.POLLIN) rxzero = 0 terminate = False read_poll_timeout = self.read_poll_timeout * 1000 # poll() timeout is in millisecs while not (terminate or self.Terminate): if self.state == LOGOUT: timeout = 1 else: timeout = read_poll_timeout try: r = poll.poll(timeout) if __debug__: self._log(5, 'poll => %s' % `r`) if not r: continue # Timeout fd,state = r[0] if state & select.POLLIN: data = self.read(self.read_size) # Drain ssl buffer if present start = 0 dlen = len(data) if __debug__: self._log(5, 'rcvd %s' % dlen) if dlen == 0: rxzero += 1 if rxzero > 5: raise IOError("Too many read 0") time.sleep(0.1) else: rxzero = 0 while True: stop = data.find('\n', start) if stop < 0: line_part += data[start:] break stop += 1 line_part, start, line = \ '', stop, line_part + data[start:stop] if __debug__: self._log(4, '< %s' % line) self.inq.put(line) if self.TerminateReader: terminate = True if state & ~(select.POLLIN): raise IOError(poll_error(state)) except: reason = 'socket error: %s - %s' % sys.exc_info()[:2] if __debug__: if not self.Terminate: self._print_log() if self.debug: self.debug += 4 # Output all self._log(1, reason) self.inq.put((self.abort, reason)) break poll.unregister(self.read_fd) if __debug__: self._log(1, 'finished') else: # No "poll" - use select() def _reader(self): threading.currentThread().setName(self.identifier + 'reader') if __debug__: self._log(1, 'starting using select') line_part = '' rxzero = 0 terminate = False while not (terminate or self.Terminate): if self.state == LOGOUT: timeout = 1 else: timeout = self.read_poll_timeout try: r,w,e = select.select([self.read_fd], [], [], timeout) if __debug__: self._log(5, 'select => %s, %s, %s' % (r,w,e)) if not r: # Timeout continue data = self.read(self.read_size) # Drain ssl buffer if present start = 0 dlen = len(data) if __debug__: self._log(5, 'rcvd %s' % dlen) if dlen == 0: rxzero += 1 if rxzero > 5: raise IOError("Too many read 0") time.sleep(0.1) else: rxzero = 0 while True: stop = data.find('\n', start) if stop < 0: line_part += data[start:] break stop += 1 line_part, start, line = \ '', stop, line_part + data[start:stop] if __debug__: self._log(4, '< %s' % line) self.inq.put(line) if self.TerminateReader: terminate = True except: reason = 'socket error: %s - %s' % sys.exc_info()[:2] if __debug__: if not self.Terminate: self._print_log() if self.debug: self.debug += 4 # Output all self._log(1, reason) self.inq.put((self.abort, reason)) break if __debug__: self._log(1, 'finished') def _writer(self): threading.currentThread().setName(self.identifier + 'writer') if __debug__: self._log(1, 'starting') reason = 'Terminated' while not self.Terminate: rqb = self.ouq.get() if rqb is None: break # Outq flushed try: self.send(rqb.data) if __debug__: self._log(4, '> %s' % rqb.data) except: reason = 'socket error: %s - %s' % sys.exc_info()[:2] if __debug__: if not self.Terminate: self._print_log() if self.debug: self.debug += 4 # Output all self._log(1, reason) rqb.abort(self.abort, reason) break self.inq.put((self.abort, reason)) if __debug__: self._log(1, 'finished') # Debugging if __debug__: def _init_debug(self, debug=None, debug_file=None, debug_buf_lvl=None): self.debug = self._choose_nonull_or_dflt(0, debug, Debug) self.debug_file = self._choose_nonull_or_dflt(sys.stderr, debug_file) self.debug_buf_lvl = self._choose_nonull_or_dflt(DFLT_DEBUG_BUF_LVL, debug_buf_lvl) self.debug_lock = threading.Lock() self._cmd_log_len = 20 self._cmd_log_idx = 0 self._cmd_log = {} # Last `_cmd_log_len' interactions if self.debug: self._mesg('imaplib2 version %s' % __version__) self._mesg('imaplib2 debug level %s, buffer level %s' % (self.debug, self.debug_buf_lvl)) def _dump_ur(self, lvl): if lvl > self.debug: return l = self.untagged_responses if not l: return t = '\n\t\t' l = map(lambda x:'%s: "%s"' % (x[0], x[1][0] and '" "'.join(x[1]) or ''), l) self.debug_lock.acquire() self._mesg('untagged responses dump:%s%s' % (t, t.join(l))) self.debug_lock.release() def _log(self, lvl, line): if lvl > self.debug: return if line[-2:] == CRLF: line = line[:-2] + '\\r\\n' tn = threading.currentThread().getName() if lvl <= 1 or self.debug > self.debug_buf_lvl: self.debug_lock.acquire() self._mesg(line, tn) self.debug_lock.release() if lvl != 1: return # Keep log of last `_cmd_log_len' interactions for debugging. self.debug_lock.acquire() self._cmd_log[self._cmd_log_idx] = (line, tn, time.time()) self._cmd_log_idx += 1 if self._cmd_log_idx >= self._cmd_log_len: self._cmd_log_idx = 0 self.debug_lock.release() def _mesg(self, s, tn=None, secs=None): if secs is None: secs = time.time() if tn is None: tn = threading.currentThread().getName() tm = time.strftime('%M:%S', time.localtime(secs)) try: self.debug_file.write(' %s.%02d %s %s\n' % (tm, (secs*100)%100, tn, s)) self.debug_file.flush() finally: pass def _print_log(self): self.debug_lock.acquire() i, n = self._cmd_log_idx, self._cmd_log_len if n: self._mesg('last %d log messages:' % n) while n: try: self._mesg(*self._cmd_log[i]) except: pass i += 1 if i >= self._cmd_log_len: i = 0 n -= 1 self.debug_lock.release() class IMAP4_SSL(IMAP4): """IMAP4 client class over SSL connection Instantiate with: IMAP4_SSL(host=None, port=None, keyfile=None, certfile=None, debug=None, debug_file=None, identifier=None, timeout=None) host - host's name (default: localhost); port - port number (default: standard IMAP4 SSL port); keyfile - PEM formatted file that contains your private key (default: None); certfile - PEM formatted certificate chain file (default: None); ca_certs - PEM formatted certificate chain file used to validate server certificates (default: None); cert_verify_cb - function to verify authenticity of server certificates (default: None); debug - debug level (default: 0 - no debug); debug_file - debug stream (default: sys.stderr); identifier - thread identifier prefix (default: host); timeout - timeout in seconds when expecting a command response. debug_buf_lvl - debug level at which buffering is turned off. For more documentation see the docstring of the parent class IMAP4. """ def __init__(self, host=None, port=None, keyfile=None, certfile=None, ca_certs=None, cert_verify_cb=None, debug=None, debug_file=None, identifier=None, timeout=None, debug_buf_lvl=None): self.keyfile = keyfile self.certfile = certfile self.ca_certs = ca_certs self.cert_verify_cb = cert_verify_cb IMAP4.__init__(self, host, port, debug, debug_file, identifier, timeout, debug_buf_lvl) def open(self, host=None, port=None): """open(host=None, port=None) Setup secure connection to remote server on "host:port" (default: localhost:standard IMAP4 SSL port). This connection will be used by the routines: read, send, shutdown, socket, ssl.""" self.host = self._choose_nonull_or_dflt('', host) self.port = self._choose_nonull_or_dflt(IMAP4_SSL_PORT, port) self.sock = self.open_socket() self.ssl_wrap_socket() def read(self, size): """data = read(size) Read at most 'size' bytes from remote.""" if self.decompressor is None: return self.sock.read(size) if self.decompressor.unconsumed_tail: data = self.decompressor.unconsumed_tail else: data = self.sock.read(8192) return self.decompressor.decompress(data, size) def send(self, data): """send(data) Send 'data' to remote.""" if self.compressor is not None: data = self.compressor.compress(data) data += self.compressor.flush(zlib.Z_SYNC_FLUSH) if hasattr(self.sock, "sendall"): self.sock.sendall(data) else: bytes = len(data) while bytes > 0: sent = self.sock.write(data) if sent == bytes: break # avoid copy data = data[sent:] bytes = bytes - sent def ssl(self): """ssl = ssl() Return socket.ssl instance used to communicate with the IMAP4 server.""" return self.sock class IMAP4_stream(IMAP4): """IMAP4 client class over a stream Instantiate with: IMAP4_stream(command, debug=None, debug_file=None, identifier=None, timeout=None, debug_buf_lvl=None) command - string that can be passed to subprocess.Popen(); debug - debug level (default: 0 - no debug); debug_file - debug stream (default: sys.stderr); identifier - thread identifier prefix (default: host); timeout - timeout in seconds when expecting a command response. debug_buf_lvl - debug level at which buffering is turned off. For more documentation see the docstring of the parent class IMAP4. """ def __init__(self, command, debug=None, debug_file=None, identifier=None, timeout=None, debug_buf_lvl=None): self.command = command self.host = command self.port = None self.sock = None self.writefile, self.readfile = None, None self.read_fd = None IMAP4.__init__(self, None, None, debug, debug_file, identifier, timeout, debug_buf_lvl) def open(self, host=None, port=None): """open(host=None, port=None) Setup a stream connection via 'self.command'. This connection will be used by the routines: read, send, shutdown, socket.""" from subprocess import Popen, PIPE self._P = Popen(self.command, shell=True, stdin=PIPE, stdout=PIPE, close_fds=True) self.writefile, self.readfile = self._P.stdin, self._P.stdout self.read_fd = self.readfile.fileno() def read(self, size): """Read 'size' bytes from remote.""" if self.decompressor is None: return os.read(self.read_fd, size) if self.decompressor.unconsumed_tail: data = self.decompressor.unconsumed_tail else: data = os.read(self.read_fd, 8192) return self.decompressor.decompress(data, size) def send(self, data): """Send data to remote.""" if self.compressor is not None: data = self.compressor.compress(data) data += self.compressor.flush(zlib.Z_SYNC_FLUSH) self.writefile.write(data) self.writefile.flush() def shutdown(self): """Close I/O established in "open".""" self.readfile.close() self.writefile.close() class _Authenticator(object): """Private class to provide en/de-coding for base64 authentication conversation.""" def __init__(self, mechinst): self.mech = mechinst # Callable object to provide/process data def process(self, data, rqb): ret = self.mech(self.decode(data)) if ret is None: return '*' # Abort conversation return self.encode(ret) def encode(self, inp): # # Invoke binascii.b2a_base64 iteratively with # short even length buffers, strip the trailing # line feed from the result and append. "Even" # means a number that factors to both 6 and 8, # so when it gets to the end of the 8-bit input # there's no partial 6-bit output. # oup = '' while inp: if len(inp) > 48: t = inp[:48] inp = inp[48:] else: t = inp inp = '' e = binascii.b2a_base64(t) if e: oup = oup + e[:-1] return oup def decode(self, inp): if not inp: return '' return binascii.a2b_base64(inp) class _IdleCont(object): """When process is called, server is in IDLE state and will send asynchronous changes.""" def __init__(self, parent, timeout): self.parent = parent self.timeout = parent._choose_nonull_or_dflt(IDLE_TIMEOUT, timeout) self.parent.idle_timeout = self.timeout + time.time() def process(self, data, rqb): self.parent.idle_lock.acquire() self.parent.idle_rqb = rqb self.parent.idle_timeout = self.timeout + time.time() self.parent.idle_lock.release() if __debug__: self.parent._log(2, 'server IDLE started, timeout in %.2f secs' % self.timeout) return None MonthNames = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] Mon2num = dict(zip((x.encode() for x in MonthNames[1:]), range(1, 13))) InternalDate = re.compile(r'.*INTERNALDATE "' r'(?P[ 0123][0-9])-(?P[A-Z][a-z][a-z])-(?P[0-9][0-9][0-9][0-9])' r' (?P[0-9][0-9]):(?P[0-9][0-9]):(?P[0-9][0-9])' r' (?P[-+])(?P[0-9][0-9])(?P[0-9][0-9])' r'"') def Internaldate2Time(resp): """time_tuple = Internaldate2Time(resp) Convert IMAP4 INTERNALDATE to UT.""" mo = InternalDate.match(resp) if not mo: return None mon = Mon2num[mo.group('mon')] zonen = mo.group('zonen') day = int(mo.group('day')) year = int(mo.group('year')) hour = int(mo.group('hour')) min = int(mo.group('min')) sec = int(mo.group('sec')) zoneh = int(mo.group('zoneh')) zonem = int(mo.group('zonem')) # INTERNALDATE timezone must be subtracted to get UT zone = (zoneh*60 + zonem)*60 if zonen == '-': zone = -zone tt = (year, mon, day, hour, min, sec, -1, -1, -1) utc = time.mktime(tt) # Following is necessary because the time module has no 'mkgmtime'. # 'mktime' assumes arg in local timezone, so adds timezone/altzone. lt = time.localtime(utc) if time.daylight and lt[-1]: zone = zone + time.altzone else: zone = zone + time.timezone return time.localtime(utc - zone) Internaldate2tuple = Internaldate2Time # (Backward compatible) def Time2Internaldate(date_time): """'"DD-Mmm-YYYY HH:MM:SS +HHMM"' = Time2Internaldate(date_time) Convert 'date_time' to IMAP4 INTERNALDATE representation.""" if isinstance(date_time, (int, float)): tt = time.localtime(date_time) elif isinstance(date_time, (tuple, time.struct_time)): tt = date_time elif isinstance(date_time, str) and (date_time[0],date_time[-1]) == ('"','"'): return date_time # Assume in correct format else: raise ValueError("date_time not of a known type") if time.daylight and tt[-1]: zone = -time.altzone else: zone = -time.timezone return ('"%2d-%s-%04d %02d:%02d:%02d %+03d%02d"' % ((tt[2], MonthNames[tt[1]], tt[0]) + tt[3:6] + divmod(zone//60, 60))) FLAGS_cre = re.compile(r'.*FLAGS \((?P[^\)]*)\)') def ParseFlags(resp): """('flag', ...) = ParseFlags(line) Convert IMAP4 flags response to python tuple.""" mo = FLAGS_cre.match(resp) if not mo: return () return tuple(mo.group('flags').split()) if __name__ == '__main__': # To test: invoke either as 'python imaplib2.py [IMAP4_server_hostname]', # or as 'python imaplib2.py -s "rsh IMAP4_server_hostname exec /etc/rimapd"' # or as 'python imaplib2.py -l "keyfile[:certfile]" [IMAP4_SSL_server_hostname]' import getopt, getpass try: optlist, args = getopt.getopt(sys.argv[1:], 'd:l:s:p:') except getopt.error, val: optlist, args = (), () debug, debug_buf_lvl, port, stream_command, keyfile, certfile = (None,)*6 for opt,val in optlist: if opt == '-d': debug = int(val) debug_buf_lvl = debug - 1 elif opt == '-l': try: keyfile,certfile = val.split(':') except ValueError: keyfile,certfile = val,val elif opt == '-p': port = int(val) elif opt == '-s': stream_command = val if not args: args = (stream_command,) if not args: args = ('',) if not port: port = (keyfile is not None) and IMAP4_SSL_PORT or IMAP4_PORT host = args[0] USER = getpass.getuser() data = open(os.path.exists("test.data") and "test.data" or __file__).read(1000) test_mesg = 'From: %(user)s@localhost%(lf)sSubject: IMAP4 test%(lf)s%(lf)s%(data)s' \ % {'user':USER, 'lf':'\n', 'data':data} test_seq1 = [ ('list', ('""', '%')), ('create', ('/tmp/imaplib2_test.0',)), ('rename', ('/tmp/imaplib2_test.0', '/tmp/imaplib2_test.1')), ('CREATE', ('/tmp/imaplib2_test.2',)), ('append', ('/tmp/imaplib2_test.2', None, None, test_mesg)), ('list', ('/tmp', 'imaplib2_test*')), ('select', ('/tmp/imaplib2_test.2',)), ('search', (None, 'SUBJECT', 'IMAP4 test')), ('fetch', ("'1:*'", '(FLAGS INTERNALDATE RFC822)')), ('store', ('1', 'FLAGS', '(\Deleted)')), ('namespace', ()), ('expunge', ()), ('recent', ()), ('close', ()), ] test_seq2 = ( ('select', ()), ('response',('UIDVALIDITY',)), ('response', ('EXISTS',)), ('append', (None, None, None, test_mesg)), ('uid', ('SEARCH', 'SUBJECT', 'IMAP4 test')), ('uid', ('SEARCH', 'ALL')), ('uid', ('THREAD', 'references', 'UTF-8', '(SEEN)')), ('recent', ()), ) AsyncError = None def responder((response, cb_arg, error)): global AsyncError cmd, args = cb_arg if error is not None: AsyncError = error M._log(0, '[cb] ERROR %s %.100s => %s' % (cmd, args, error)) return typ, dat = response M._log(0, '[cb] %s %.100s => %s %.100s' % (cmd, args, typ, dat)) if typ == 'NO': AsyncError = (Exception, dat[0]) def run(cmd, args, cb=True): if AsyncError: M._log(1, 'AsyncError') M.logout() typ, val = AsyncError raise typ(val) if not M.debug: M._log(0, '%s %.100s' % (cmd, args)) try: if cb: typ, dat = getattr(M, cmd)(callback=responder, cb_arg=(cmd, args), *args) M._log(1, '%s %.100s => %s %.100s' % (cmd, args, typ, dat)) else: typ, dat = getattr(M, cmd)(*args) M._log(1, '%s %.100s => %s %.100s' % (cmd, args, typ, dat)) except: M._log(1, '%s - %s' % sys.exc_info()[:2]) M.logout() raise if typ == 'NO': M._log(1, 'NO') M.logout() raise Exception(dat[0]) return dat try: threading.currentThread().setName('main') if keyfile is not None: if not keyfile: keyfile = None if not certfile: certfile = None M = IMAP4_SSL(host=host, port=port, keyfile=keyfile, certfile=certfile, debug=debug, identifier='', timeout=10, debug_buf_lvl=debug_buf_lvl) elif stream_command: M = IMAP4_stream(stream_command, debug=debug, identifier='', timeout=10, debug_buf_lvl=debug_buf_lvl) else: M = IMAP4(host=host, port=port, debug=debug, identifier='', timeout=10, debug_buf_lvl=debug_buf_lvl) if M.state != 'AUTH': # Login needed PASSWD = getpass.getpass("IMAP password for %s on %s: " % (USER, host or "localhost")) test_seq1.insert(0, ('login', (USER, PASSWD))) M._log(0, 'PROTOCOL_VERSION = %s' % M.PROTOCOL_VERSION) if 'COMPRESS=DEFLATE' in M.capabilities: M.enable_compression() for cmd,args in test_seq1: run(cmd, args) for ml in run('list', ('/tmp/', 'imaplib2_test%'), cb=False): mo = re.match(r'.*"([^"]+)"$', ml) if mo: path = mo.group(1) else: path = ml.split()[-1] run('delete', (path,)) for cmd,args in test_seq2: if (cmd,args) != ('uid', ('SEARCH', 'SUBJECT', 'IMAP4 test')): run(cmd, args) continue dat = run(cmd, args, cb=False) uid = dat[-1].split() if not uid: continue run('uid', ('FETCH', uid[-1], '(FLAGS INTERNALDATE RFC822.SIZE RFC822.HEADER RFC822.TEXT)')) run('uid', ('STORE', uid[-1], 'FLAGS', '(\Deleted)')) run('expunge', ()) if 'IDLE' in M.capabilities: run('idle', (2,), cb=False) run('idle', (99,), cb=True) # Asynchronous, to test interruption of 'idle' by 'noop' time.sleep(1) run('noop', (), cb=False) run('logout', (), cb=False) if debug: M._mesg('') M._print_log() M._mesg('') M._mesg('unused untagged responses in order, most recent last:') for typ,dat in M.pop_untagged_responses(): M._mesg('\t%s %s' % (typ, dat)) print 'All tests OK.' except: print 'Tests failed.' if not debug: print ''' If you would like to see debugging output, try: %s -d5 ''' % sys.argv[0] raise Mailnag/daemon/mail.py0000664000175000017500000000212512072264260015044 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # mail.py # # Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # class Mail: def __init__(self, seconds, subject, sender, datetime, id, account_id): self.seconds = seconds self.subject = subject self.sender = sender self.datetime = datetime self.id = id self.account_id = account_id Mailnag/daemon/idler.py0000664000175000017500000001056312072264260015226 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # idler.py # # Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Leighton Earl # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # import threading import time import sys from daemon.imaplib2 import AUTH class Idler(object): def __init__(self, account, sync_callback): self.RECONNECT_RETRY_INTERVAL = 5 # minutes self._thread = threading.Thread(target=self._idle) self._event = threading.Event() self._sync_callback = sync_callback self._account = account # use_existing = True: # connection has been opened in mailnag.py already (immediate check) self._conn = account.get_connection(use_existing = True) self._disposed = False if self._conn == None: raise Exception("Failed to establish a connection for account '%s'" % account.name) # Need to get out of AUTH mode of fresh connections. if self._conn.state == AUTH: self._select(self._conn, account.folder) def run(self): if self._disposed: raise Exception("Idler has been disposed") self._thread.start() def dispose(self): if self._thread.is_alive(): self._event.set() self._thread.join() try: if self._conn != None: # (calls idle_callback) self._conn.close() # shutdown existing callback thread self._conn.logout() except: pass self._disposed = True print "Idler closed" # idle thread def _idle(self): while True: # if the event is set here, # disposed() must have been called # so stop the idle thread. if self._event.isSet(): return self._needsync = False self._conn_closed = False # register idle callback that is called whenever an idle event arrives (new mail / mail deleted). # the callback is called after 10 minutes at the latest. gmail sends keepalive events every 5 minutes. self._conn.idle(callback = self._idle_callback, timeout = 60 * 10) # waits for the event to be set # (in idle callback or in dispose()) self._event.wait() # if the event is set due to idle sync if self._needsync: self._event.clear() if self._conn_closed: self._reconnect() if self._conn != None: self._sync_callback(self._account) # idle callback (runs on a further thread) def _idle_callback(self, args): # check if the connection has been reset by provider self._conn_closed = (args[2] != None) and (args[2][0] is self._conn.abort) # flag that a mail sync is needed self._needsync = True # trigger waiting _idle thread self._event.set() def _reconnect(self): # connection has been reset by provider -> try to reconnect print "Idler thread for account '%s' has been disconnected" % self._account.name # conn has already been closed, don't try to close it again # self._conn.close() # (calls idle_callback) # shutdown existing callback thread self._conn.logout() self._conn = None while (self._conn == None) and (not self._event.isSet()): sys.stdout.write("Trying to reconnect Idler thread for account '%s'..." % self._account.name) self._conn = self._account.get_connection(use_existing = False) if self._conn == None: sys.stdout.write("FAILED\n") print "Trying again in %s minutes" % self.RECONNECT_RETRY_INTERVAL self._wait(60 * self.RECONNECT_RETRY_INTERVAL) # don't hammer the server else: sys.stdout.write("OK\n") if self._conn != None: self._select(self._conn, self._account.folder) def _select(self, conn, folder): folder = folder.strip() if len(folder) > 0: conn.select(folder) else: conn.select("INBOX") def _wait(self, secs): start_time = time.time() while (((time.time() - start_time) < secs) and (not self._event.isSet())): time.sleep(1) Mailnag/daemon/mails.py0000664000175000017500000002435712072264260015242 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # mails.py # # Copyright 2011, 2012 Patrick Ulbrich # Copyright 2011 Leighton Earl # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # import time import sys import email from common.i18n import _ from email.header import decode_header from daemon.mail import Mail class Mails: def __init__(self, cfg, accounts): self._cfg = cfg self._accounts = accounts def get_mail(self, sort_order = None): mail_list = [] mail_ids = [] filter_enabled = bool(int(self._cfg.get('filter', 'filter_enabled'))) for acc in self._accounts: # get server connection for this account srv = acc.get_connection(use_existing = True) if srv == None: continue elif acc.imap:# IMAP if len(acc.folder.strip()) == 0: folder_list = ["INBOX"] else: folder_list = acc.folder.split(',') for folder in folder_list: folder = folder.strip() if len(folder) == 0: continue # select IMAP folder srv.select(folder, readonly=True) try: status, data = srv.search(None, 'UNSEEN') # ALL or UNSEEN except: print "The folder:", folder, "does not exist, using INBOX instead" try: # if search fails select INBOX and try again srv.select('INBOX', readonly=True) status, data = srv.search(None, 'UNSEEN') # ALL or UNSEEN except: print "INBOX Could not be found", sys.exc_info()[0] if status != 'OK' or None in [d for d in data]: print "Folder", folder, "in status", status, "| Data:", data, "\n" continue # Bugfix LP-735071 for num in data[0].split(): typ, msg_data = srv.fetch(num, '(BODY.PEEK[HEADER])') # header only (without setting READ flag) for response_part in msg_data: if isinstance(response_part, tuple): try: msg = email.message_from_string(response_part[1]) except: print "Could not get IMAP message." # debug continue try: try: # get sender and format it sender = self._format_header('sender', msg['From']) except KeyError: print "KeyError exception for key 'From' in message." # debug sender = self._format_header('sender', msg['from']) except: print "Could not get sender from IMAP message." # debug sender = "Error in sender" try: try: # get date and format it datetime, seconds = self._format_header('date', msg['Date']) except KeyError: print "KeyError exception for key 'Date' in message." # debug datetime, seconds = self._format_header('date', msg['date']) except: print "Could not get date from IMAP message." # debug # take current time as "2010.12.31 13:57:04" datetime = time.strftime('%Y.%m.%d %X') # current time to seconds seconds = time.time() try: try: # get subject and format it subject = self._format_header('subject', msg['Subject']) except KeyError: print "KeyError exception for key 'Subject' in message." # debug subject = self._format_header('subject', msg['subject']) except: print "Could not get subject from IMAP message." # debug subject = _('No subject') try: id = msg['Message-Id'] except: print "Could not get id from IMAP message." # debug id = None if id == None or id == '': # create fallback id id = str(hash(acc.server + acc.user + sender + subject)) # prevent duplicates caused by Gmail labels if id not in mail_ids: if not (filter_enabled and self._in_filter(sender + subject)): # check filter mail_list.append(Mail(seconds, subject, \ sender, datetime, id, acc.get_id())) mail_ids.append(id) # don't close IMAP idle connections if not acc.idle: srv.close() srv.logout() else: # POP # number of mails on the server mail_total = len(srv.list()[1]) for i in range(1, mail_total+1): # for each mail try: # header plus first 0 lines from body message = srv.top(i, 0)[1] except: print "Could not get POP message." # debug continue # convert list to string message_string = '\n'.join(message) try: # put message into email object and make a dictionary msg = dict(email.message_from_string(message_string)) except: print "Could not get msg from POP message." # debug continue try: try: # get sender and format it sender = self._format_header('sender', msg['From']) except KeyError: print "KeyError exception for key 'From' in message." # debug sender = self._format_header('sender', msg['from']) except: print "Could not get sender from POP message." # debug sender = "Error in sender" try: try: # get date and format it datetime, seconds = self._format_header('date', msg['Date']) except KeyError: print "KeyError exception for key 'Date' in message." # debug datetime, seconds = self._format_header('date', msg['date']) except: print "Could not get date from POP message." # debug # take current time as "2010.12.31 13:57:04" datetime = time.strftime('%Y.%m.%d %X') # current time to seconds seconds = time.time() try: try: # get subject and format it subject = self._format_header('subject', msg['Subject']) except KeyError: print "KeyError exception for key 'Subject' in message." # debug subject = self._format_header('subject', msg['subject']) except: print "Could not get subject from POP message." subject = _('No subject') try: # get id uidl = srv.uidl(i) except: print "Could not get id from POP message." # debug uidl = None if uidl == None or uidl == '': # create fallback id id = str(hash(acc.server + acc.user + sender + subject)) else: # create unique id id = acc.user + uidl.split(' ')[2] if not (filter_enabled and self._in_filter(sender + subject)): # check filter mail_list.append(Mail(seconds, subject, sender, \ datetime, id, acc.get_id())) # disconnect from Email-Server srv.quit() if (sort_order != None): # sort mails mail_list = self.sort_mails(mail_list, sort_order) # write stdout to log file sys.stdout.flush() return mail_list # check if filter appears in sendersubject def _in_filter(self, sendersubject): status = False filter_text = self._cfg.get('filter', 'filter_text') # convert text to list filter_list = filter_text.replace('\n', '').split(',') for filter_item in filter_list: # remove CR and white space filter_stripped_item = filter_item.strip() if len(filter_stripped_item) == 0: continue if filter_stripped_item.lower() in sendersubject.lower(): # subject contains filter item status = True break return status # sort mail list by field 'seconds' @staticmethod def sort_mails(mail_list, sort_order): sort_list = [] for mail in mail_list: sort_list.append([mail.seconds, mail]) # sort asc sort_list.sort() if sort_order == 'desc': # sort desc sort_list.reverse() # recreate mail_list mail_list = [] for mail in sort_list: mail_list.append(mail[1]) return mail_list # format sender, date, subject etc. def _format_header(self, field, content): if field == 'sender': try: # get the two parts of the sender sender_real, sender_addr = email.utils.parseaddr(content) sender_real = self._convert(sender_real) sender_addr = self._convert(sender_addr) # create decoded tupel sender = (sender_real, sender_addr) except: sender = ('','Error: cannot format sender') sender_format = self._cfg.get('general', 'sender_format') if sender_format == '1' and sender[0] != '': # real sender name if not empty sender = sender_real else: sender = sender_addr return sender if field == 'date': try: # make a 10-tupel (UTC) parsed_date = email.utils.parsedate_tz(content) # convert 10-tupel to seconds incl. timezone shift seconds = email.utils.mktime_tz(parsed_date) # convert seconds to tupel tupel = time.localtime(seconds) # convert tupel to string datetime = time.strftime('%Y.%m.%d %X', tupel) except: print 'Error: cannot format date.' # take current time as "2010.12.31 13:57:04" datetime = time.strftime('%Y.%m.%d %X') # current time to seconds seconds = time.time() return datetime, seconds if field == 'subject': try: subject = self._convert(content) except: subject = 'Error: cannot format subject' return subject # decode and concatenate multi-coded header parts def _convert(self, raw_content): # replace newline by space content = raw_content.replace('\n',' ') # workaround a bug in email.header.decode_header() content = content.replace('?==?','?= =?') # list of (text_part, charset) tupels tupels = decode_header(content) content_list = [] # iterate trough parts for text, charset in tupels: # set default charset for decoding if charset == None: charset = 'latin-1' # replace non-decodable chars with 'nothing' content_list.append(text.decode(charset, 'ignore')) # insert blanks between parts decoded_content = u' '.join(content_list) # get rid of whitespace decoded_content = decoded_content.strip() return decoded_content Mailnag/daemon/pid.py0000664000175000017500000000233212072264260014676 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # pid.py # # Copyright 2011 Patrick Ulbrich # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # # List class to manage subprocess PIDs class Pid(list): # kill all zombies def kill(self): # list of PIDs to remove from list removals = [] for p in self: # get returncode of subprocess returncode = p.poll() # zombie will be removed if returncode == 0: removals.append(p) # remove non-zombies from list for p in removals: self.remove(p) Mailnag/daemon/mailsyncer.py0000664000175000017500000000455412072264260016300 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # mailsyncer.py # # Copyright 2012 Patrick Ulbrich # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # from daemon.mails import Mails class MailSyncer: def __init__(self, cfg): self._cfg = cfg self._mails_by_account = {} self._mail_list = [] def sync(self, accounts): needs_rebuild = False # get mails from given accounts rcv_lst = Mails(self._cfg, accounts).get_mail() # group received mails by account tmp = {} for acc in accounts: tmp[acc.get_id()] = {} for mail in rcv_lst: tmp[mail.account_id][mail.id] = mail # compare current mails against received mails # and remove those that are gone (probably opened in mail client). for acc_id in self._mails_by_account.iterkeys(): if acc_id in tmp: del_ids = [] for mail_id in self._mails_by_account[acc_id].iterkeys(): if not (mail_id in tmp[acc_id]): del_ids.append(mail_id) needs_rebuild = True for mail_id in del_ids: del self._mails_by_account[acc_id][mail_id] # compare received mails against current mails # and add new mails. for acc_id in tmp: if not (acc_id in self._mails_by_account): self._mails_by_account[acc_id] = {} for mail_id in tmp[acc_id]: if not (mail_id in self._mails_by_account[acc_id]): self._mails_by_account[acc_id][mail_id] = tmp[acc_id][mail_id] needs_rebuild = True # rebuild and sort mail list if needs_rebuild: self._mail_list = [] for acc_id in self._mails_by_account: for mail_id in self._mails_by_account[acc_id]: self._mail_list.append(self._mails_by_account[acc_id][mail_id]) self._mail_list = Mails.sort_mails(self._mail_list, 'desc') return self._mail_list Mailnag/daemon/__init__.py0000664000175000017500000000147012072264260015663 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # __init__.py # # Copyright 2011 Patrick Ulbrich # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # Mailnag/configuration/0000775000175000017500000000000012072264260015154 5ustar patrickpatrickMailnag/configuration/accountdialog.py0000664000175000017500000001171212072264260020344 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # accountdialog.py # # Copyright 2011, 2012 Patrick Ulbrich # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # from gi.repository import GLib, GdkPixbuf, Gtk from common.dist_cfg import PACKAGE_NAME from common.i18n import _ from common.utils import get_data_file from common.account import Account class AccountDialog: def __init__(self, parent, acc): self._acc = acc builder = Gtk.Builder() builder.set_translation_domain(PACKAGE_NAME) builder.add_from_file(get_data_file("account_dialog.ui")) builder.connect_signals({ \ "account_type_changed" : self._on_cmb_account_type_changed, \ "entry_changed" : self._on_entry_changed, \ "btn_cancel_clicked" : self._on_btn_cancel_clicked, \ "btn_save_clicked" : self._on_btn_save_clicked \ }) self._window = builder.get_object("account_dialog") self._window.set_transient_for(parent) self._cmb_account_type = builder.get_object("cmb_account_type") self._entry_account_name = builder.get_object("entry_account_name") self._entry_account_user = builder.get_object("entry_account_user") self._entry_account_password = builder.get_object("entry_account_password") self._entry_account_server = builder.get_object("entry_account_server") self._entry_account_port = builder.get_object("entry_account_port") self._label_account_folder = builder.get_object("label_account_folder") self._entry_account_folder = builder.get_object("entry_account_folder") self._chk_account_push = builder.get_object("chk_account_push") self._chk_account_ssl = builder.get_object("chk_account_ssl") self._button_save = builder.get_object("button_save") self._entry_account_port.set_placeholder_text(_("optional")) self._entry_account_folder.set_placeholder_text(_("optional")) def run(self): self._cmb_account_type.set_active(int(self._acc.imap)) self._entry_account_name.set_text(self._acc.name) self._entry_account_user.set_text(self._acc.user) self._entry_account_password.set_text(self._acc.password) self._entry_account_server.set_text(self._acc.server) self._entry_account_port.set_text(self._acc.port) self._entry_account_folder.set_text(self._acc.folder) self._chk_account_push.set_active(self._acc.idle) self._chk_account_ssl.set_active(self._acc.ssl) res = self._window.run() if res == 1: self._acc.name = self._entry_account_name.get_text() self._acc.user = self._entry_account_user.get_text() self._acc.password = self._entry_account_password.get_text() self._acc.server = self._entry_account_server.get_text() self._acc.port = self._entry_account_port.get_text() self._acc.ssl = self._chk_account_ssl.get_active() if self._cmb_account_type.get_active() == 0: # POP3 self._acc.imap = False self._acc.folder = '' self._acc.idle = False else: # IMAP self._acc.imap = True self._acc.folder = self._entry_account_folder.get_text() self._acc.idle = self._chk_account_push.get_active() self._window.destroy() return res def get_account(self): return self._acc def _on_btn_cancel_clicked(self, widget): pass def _on_btn_save_clicked(self, widget): pass def _on_entry_changed(self, widget): if widget is self._entry_account_folder: # disable IMAP Push checkbox if multiple folders are specifed if ("," in self._entry_account_folder.get_text()): self._chk_account_push.set_active(False) self._chk_account_push.set_sensitive(False) else: self._chk_account_push.set_sensitive(True) else: # validate ok = len(self._entry_account_name.get_text()) > 0 and \ len(self._entry_account_user.get_text()) > 0 and \ len(self._entry_account_password.get_text()) > 0 and \ len(self._entry_account_server.get_text()) > 0 self._button_save.set_sensitive(ok) def _on_cmb_account_type_changed(self, widget): if self._cmb_account_type.get_active() == 0: # POP3 self._label_account_folder.set_visible(False) self._entry_account_folder.set_visible(False) self._chk_account_push.set_visible(False) else: # IMAP self._label_account_folder.set_visible(True) self._entry_account_folder.set_visible(True) self._chk_account_push.set_visible(True) Mailnag/configuration/configwindow.py0000664000175000017500000002537712072264260020241 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # configwindow.py # # Copyright 2011 - 2013 Patrick Ulbrich # Copyright 2011 Ralf Hersel # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # import os import xdg.BaseDirectory as bd from gi.repository import GLib, GdkPixbuf, Gtk, GObject from common.dist_cfg import PACKAGE_NAME, APP_VERSION from common.i18n import _ from common.utils import get_data_file from common.config import read_cfg, write_cfg from common.accountlist import AccountList from common.account import Account from configuration.accountdialog import AccountDialog class ConfigWindow: def __init__(self): builder = Gtk.Builder() builder.set_translation_domain(PACKAGE_NAME) builder.add_from_file(get_data_file("config_window.ui")) builder.connect_signals({ \ "config_window_deleted" : self._on_config_window_deleted, \ "btn_add_clicked" : self._on_btn_add_clicked, \ "btn_edit_clicked" : self._on_btn_edit_clicked, \ "btn_remove_clicked" : self._on_btn_remove_clicked, \ "treeview_accounts_row_activated" : self._on_treeview_accounts_row_activated, \ "liststore_accounts_row_deleted" : self._on_liststore_accounts_row_deleted, \ "liststore_accounts_row_inserted" : self._on_liststore_accounts_row_inserted, \ "chk_enable_filter_toggled" : self._on_chk_enable_filter_toggled, \ "chk_script0_toggled" : self._on_chk_script0_toggled, \ "chk_script1_toggled" : self._on_chk_script1_toggled \ }) self._window = builder.get_object("config_window") self._window.set_icon(GdkPixbuf.Pixbuf.new_from_file_at_size(get_data_file("mailnag.svg"), 48, 48)); self._cfg = read_cfg() # # account tab # self._accounts = AccountList() self._treeview_accounts = builder.get_object("treeview_accounts") self._liststore_accounts = builder.get_object("liststore_accounts") self._button_edit = builder.get_object("button_edit") self._button_remove = builder.get_object("button_remove") renderer_on = Gtk.CellRendererToggle() renderer_on.connect("toggled", self._on_account_toggled) column_on = Gtk.TreeViewColumn(_('Enabled'), renderer_on) column_on.add_attribute(renderer_on, "active", 1) column_on.set_alignment(0.5) self._treeview_accounts.append_column(column_on) renderer_name = Gtk.CellRendererText() column_name = Gtk.TreeViewColumn(_('Name'), renderer_name, text=2) self._treeview_accounts.append_column(column_name) # # general tab # self._spinbutton_interval = builder.get_object("spinbutton_interval") self._cb_notification_mode = builder.get_object("cb_notification_mode") cell = Gtk.CellRendererText() self._cb_notification_mode.pack_start(cell, True) self._cb_notification_mode.add_attribute(cell, "text", 0) self._chk_playsound = builder.get_object("chk_playsound") self._chk_autostart = builder.get_object("chk_autostart") # # spam filter tab # self._chk_enable_filter = builder.get_object("chk_enable_filter") self._textview_filter = builder.get_object("textview_filter") self._textbuffer_filter = builder.get_object("textbuffer_filter") # # events tab # self._chk_script0 = builder.get_object("chk_script0") self._filechooser_script0 = builder.get_object("filechooser_script0") self._chk_script1 = builder.get_object("chk_script1") self._filechooser_script1 = builder.get_object("filechooser_script1") # # about tab # self._image_logo = builder.get_object("image_logo") pb = GdkPixbuf.Pixbuf.new_from_file_at_size(get_data_file("mailnag.svg"), 180, 180) pb = pb.new_subpixbuf(0, 10, 180, 146) # crop whitespace at the bottom self._image_logo.set_from_pixbuf(pb) self._label_app_desc = builder.get_object("label_app_desc") self._label_app_desc.set_markup("Mailnag\nVersion %s" % str(APP_VERSION)) self._load_config() self._window.show() def _load_config(self): self._spinbutton_interval.set_value(int(self._cfg.get('general', 'check_interval'))) self._cb_notification_mode.set_active(int(self._cfg.get('general', 'notification_mode'))) self._chk_playsound.set_active(bool(int(self._cfg.get('general', 'playsound')))) self._chk_autostart.set_active(bool(int(self._cfg.get('general', 'autostart')))) self._chk_enable_filter.set_active(bool(int(self._cfg.get('filter', 'filter_enabled')))) self._textbuffer_filter.set_text(self._cfg.get('filter', 'filter_text')) self._chk_script0.set_active(bool(int(self._cfg.get('script', 'script0_enabled')))) tmp = self._cfg.get('script', 'script0_file') if len(tmp) > 0: self._filechooser_script0.set_filename(tmp) self._chk_script1.set_active(bool(int(self._cfg.get('script', 'script1_enabled')))) tmp = self._cfg.get('script', 'script1_file') if len(tmp) > 0: self._filechooser_script1.set_filename(tmp) self._accounts.load_from_cfg(self._cfg) for acc in self._accounts: row = [acc, acc.enabled, acc.name] self._liststore_accounts.append(row) self._select_path((0,)) def _save_config(self): self._cfg.set('general', 'check_interval', int(self._spinbutton_interval.get_value())) self._cfg.set('general', 'notification_mode', int(self._cb_notification_mode.get_active())) self._cfg.set('general', 'playsound',int(self._chk_playsound.get_active())) autostart = self._chk_autostart.get_active() self._cfg.set('general', 'autostart', int(autostart)) self._cfg.set('filter', 'filter_enabled', int(self._chk_enable_filter.get_active())) start, end = self._textbuffer_filter.get_bounds() self._cfg.set('filter', 'filter_text', self._textbuffer_filter.get_text(start, end, True)) self._cfg.set('script', 'script0_enabled', int(self._chk_script0.get_active())) tmp = self._filechooser_script0.get_filename() if tmp == None: tmp = "" self._cfg.set('script', 'script0_file', tmp) self._cfg.set('script', 'script1_enabled', int(self._chk_script1.get_active())) tmp = self._filechooser_script1.get_filename() if tmp == None: tmp = "" self._cfg.set('script', 'script1_file', tmp) self._accounts.save_to_cfg(self._cfg) write_cfg(self._cfg) if autostart: self._create_autostart() else: self._delete_autostart() def _show_yesno_dialog(self, text): message = Gtk.MessageDialog(self._window, Gtk.DialogFlags.MODAL, \ Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, text) resp = message.run() message.destroy() if resp == Gtk.ResponseType.YES: return True else: return False def _get_selected_account(self): treeselection = self._treeview_accounts.get_selection() selection = treeselection.get_selected() model, iter = selection # get account object from treeviews 1. column if iter != None: acc = model.get_value(iter, 0) else: acc = None return acc, model, iter def _select_path(self, path): treeselection = self._treeview_accounts.get_selection() treeselection.select_path(path) self._treeview_accounts.grab_focus() def _edit_account(self): acc, model, iter = self._get_selected_account() if iter != None: d = AccountDialog(self._window, acc) if d.run() == 1: model.set_value(iter, 2, acc.name) def _create_autostart(self): # get current working directory curdir = os.getcwd() # path to mailnag startscript exec_file = os.path.join(curdir, "mailnag") content = "\n" + \ "[Desktop Entry]\n" + \ "Type=Application\n" + \ "Exec=" + exec_file + "\n" + \ "Hidden=false\n" + \ "NoDisplay=false\n" + \ "X-GNOME-Autostart-enabled=true\n" + \ "Name=Mailnag\n" + \ "Comment=Email notifier for GNOME 3\n" \ "OnlyShowIn=GNOME;\n" \ "AutostartCondition=GNOME3 if-session gnome" autostart_folder = os.path.join(bd.xdg_config_home, "autostart") if not os.path.exists(autostart_folder): os.makedirs(autostart_folder) autostart_file = os.path.join(autostart_folder, "mailnag.desktop") f = open(autostart_file, 'w') # create file f.write(content) f.close() def _delete_autostart(self): autostart_folder = os.path.join(bd.xdg_config_home, "autostart") autostart_file = autostart_folder + "mailnag.desktop" if os.path.exists(autostart_file): os.remove(autostart_file) def _on_account_toggled(self, cell, path): model = self._liststore_accounts iter = model.get_iter(path) acc = model.get_value(iter, 0) acc.enabled = not acc.enabled self._liststore_accounts.set_value(iter, 1, not cell.get_active()) def _on_btn_add_clicked(self, widget): acc = Account(enabled = True, name = '') d = AccountDialog(self._window, acc) if d.run() == 1: self._accounts.append(acc) row = [acc, acc.enabled, acc.name] iter = self._liststore_accounts.append(row) model = self._treeview_accounts.get_model() path = model.get_path(iter) self._treeview_accounts.set_cursor(path, None, False) self._treeview_accounts.grab_focus() def _on_btn_edit_clicked(self, widget): self._edit_account() def _on_btn_remove_clicked(self, widget): acc, model, iter = self._get_selected_account() if iter != None: if self._show_yesno_dialog(_('Delete this account:') + \ '\n\n' + acc.name): # select prev/next account p = model.get_path(iter) if not p.prev(): p.next() self._select_path(p) # remove from treeview model.remove(iter) # remove from accounts list self._accounts.remove(acc) def _on_treeview_accounts_row_activated(self, treeview, path, view_column): self._edit_account() def _on_liststore_accounts_row_deleted(self, model, path): self._button_edit.set_sensitive(len(model) > 0) self._button_remove.set_sensitive(len(model) > 0) def _on_liststore_accounts_row_inserted(self, model, path, user_param): self._button_edit.set_sensitive(len(model) > 0) self._button_remove.set_sensitive(len(model) > 0) def _on_chk_enable_filter_toggled(self, widget): self._textview_filter.set_sensitive(self._chk_enable_filter.get_active()) def _on_chk_script0_toggled(self, widget): self._filechooser_script0.set_sensitive(self._chk_script0.get_active()) def _on_chk_script1_toggled(self, widget): self._filechooser_script1.set_sensitive(self._chk_script1.get_active()) def _save_and_quit(self): self._save_config() Gtk.main_quit() def _on_config_window_deleted(self, widget, event): self._save_and_quit() Mailnag/configuration/__init__.py0000664000175000017500000000147012072264260017267 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # __init__.py # # Copyright 2011 Patrick Ulbrich # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # Mailnag/__init__.py0000664000175000017500000000147012072264260014420 0ustar patrickpatrick#!/usr/bin/env python # -*- coding: utf-8 -*- # # __init__.py # # Copyright 2012 Patrick Ulbrich # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # mailnag_config0000775000175000017500000000027712072264260013626 0ustar patrickpatrick#!/bin/bash LIB_DIR=./Mailnag cd $(dirname $(readlink -f $0)) python $LIB_DIR/config.py if [ $? -eq 0 ]; then # Restart mailnag daemon ./mailnag & else echo mailnag-config discarded fi NEWS0000664000175000017500000000562612072264260011445 0ustar patrickpatrickVersion 0.5.2 (2013-01-06): =========================== * Fixed path of the autstart file * Minor other fixes Version 0.5.1 (2012-12-23): =========================== * Restored translated strings (previously available in mailnag <= 0.4.3) that were removed due to a bug in the gen_po_template script * Fixed a race condition that can lead to mutliple mailnag instances Version 0.5.0 (2012-12-05): =========================== * Bugfix: (really) don't crash on session start if the notification DBUS interface isn't available yet * Migrated to the new keyring gir binding * Removed evolution account import (not working anymore) * Minor other fixes * Updated translations IMPORTANT NOTES: - Packagers should incorporate the new dependecy list. - The new keyring binding stores credentials in ~/.local/share/keyrings instead of in ~/.gnome2/keyrings. So you probably have to fire up mailnag_config and re-enter your mail account password(s). Version 0.4.4 (2012-10-20): =========================== * Bugfix: don't crash on session start if the notification DBUS interface isn't available yet * Bugfix: fix notification sound playback in GNOME 3.6 * Removed messagetray-label configuration since gnome-shell no longer shows labels in the messagetray Version 0.4.3 (2012-09-22): =========================== * Added installation script (setup.py) * IMAP related bugfixes * Updated translations Version 0.4.2 (2012-07-10): =========================== * Bugfix: enable gettext fallback language * Updated translations Version 0.4.1 (2012-05-21): =========================== * Fixed some crashes and connection issues * Use unicode for translated strings * Don't play notification sounds when GNOME Shell notifications are disabled * Updated translations Version 0.4 (2012-01-15): ========================= * Much improved IMAP IDLE support * Reconnect if a connection has been lost (e.g. after standby) * Use GNOMEs default mail client * Enable SSL by default * Use a meaningful messagetray label by default * Added version info to the about tab * Refactoring, removed unused code * Updated translations * Bugfixes Version 0.3 (2011-11-27): ========================= * Support for IMAP-Push notifications (thanks tony747!) * Single/summary notification modes * Support for GNOME 3.2 notification counters (single mode only) * Mails can be marked as read * Explicit SSL encryption * Autostart in GNOME sessions only * Detection of default email client * Notification sound playback via GStreamer (ogg) * Removed GTK2 workaround code * Lots of bugfixes, rewritten code and refactoring * New translations Please note that this release breaks existing config files (for the first and last time), so make sure to delete ~/.config/mailnag/mailnag.cfg before upgrading. Version 0.2 (2011-10-17): ========================= * Added many new translations * Bugfixes Version 0.1 (2011-07-06): ========================= * Initial release po/0000775000175000017500000000000012072264260011353 5ustar patrickpatrickpo/fr.po0000664000175000017500000001142012072264260012320 0ustar patrickpatrick# French translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: Philippe Poumaroux \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:13+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" "Language: \n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Non nommé" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(et {0} de plus)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Vous avez {0} nouveaux courriels." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Vous avez un nouveau courriel." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "Marquer comme lu" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "Sans objet" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "facultatif" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Activé" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Nom" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Supprimer ce compte:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Compte de messagerie" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "Port:" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Serveur:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Mot de passe:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Utilisateur:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Nom du compte:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "Dossiers:" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "Activer le Push-IMAP" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "Type de compte :" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "Activer l'encryptage SSL" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configuration de Mailnag" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Comptes" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Jouer un son en cas de nouveaux courriels" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Démarrer Mailnag automatiquement" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Intervalle de vérification :" #: data/config_window.ui.h:6 msgid "minutes" msgstr "minutes" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "Mode de notification:" #: data/config_window.ui.h:8 msgid "General" msgstr "Général" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Activer le filtre anti pourriels" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Filtre anti pourriels" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "Lors de la vérification du courrier" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Le script suivant sera exécuté à chaque vérification de courriel.\n" "Mailnag envoie le nombre total de nouveaux courriels à ce script,\n" "suivi par les paires \"<émetteur> <titre>\"." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "Lors de l'arrivée d'un courriel" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Le script suivant sera exécuté à chaque fois que de nouveaux courriels " "arrivent.\n" "Mailnag envoie le nombre total de nouveaux courriels à ce script,\n" "suivi par les paires \"<émetteur> <titre>\"." #: data/config_window.ui.h:19 msgid "Events" msgstr "Evénements" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "A propos" #: data/config_window.ui.h:24 msgid "Single" msgstr "Unique" #: data/config_window.ui.h:25 msgid "Summary" msgstr "Résumé" po/nb.po0000664000175000017500000001070012072264260012310 0ustar patrickpatrick# Norwegian Bokmal translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:43+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Norwegian Bokmal \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Ikke navngitt" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(og {0} flere)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Du har {0} nye e-poster." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Du har en ny e-post." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Aktivert" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Navn" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Slett denne kontoen:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "E-post konto" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Tjener" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Passord" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Bruker:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Kontonavn:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag konfigurasjon" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Kontoer" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Spill av lyd ved nye e-poster" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Start Mailnag automatisk" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Sjekkeintervall:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "minutter" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "" #: data/config_window.ui.h:8 msgid "General" msgstr "Genrelt" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Aktiver spamfilter" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Spamfilter" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "Ved e-post sjekk" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Følgende skript vil bli kjørt ved hver enkelt e-post sjekk.\n" "Mailnag sender det totale antall nye e-poster til dette skriptet,\n" "fulgt av \"<avsender> <emne>\" par." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "Ved e-post ankomst" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Følgende skript vil bli kjørt hver gang nye e-poster ankommer.\n" "Mailnag sender det totale antall nye e-poster til dette skriptet,\n" "fulgt av \"<avsender> <emne>\" par." #: data/config_window.ui.h:19 msgid "Events" msgstr "Hendelser" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "Om" #: data/config_window.ui.h:24 msgid "Single" msgstr "" #: data/config_window.ui.h:25 msgid "Summary" msgstr "" po/vi.po0000664000175000017500000001013212072264260012326 0ustar patrickpatrick# Vietnamese translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 18:05+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Vietnamese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Chưa có tên" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(và {0} nữa)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Bạn có {0} thư mới." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Bạn có một thư mới." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Bật" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Tên" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Xóa tài khoản này:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Tài khoản thư" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Máy chủ" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Mật khẩu:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Người dùng:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Tên tài khoản:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Cấu hình Mailnag" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Tài khoản" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Phát âm thanh khi có thư mới" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Tự động chạy Mailnag" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Thời gian kiểm tra thư:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "phút" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "" #: data/config_window.ui.h:8 msgid "General" msgstr "Chung" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Bật bộ lọc thư rác" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Bộ lọc thư rác" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" #: data/config_window.ui.h:19 msgid "Events" msgstr "Sự kiện" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "Giới thiệu" #: data/config_window.ui.h:24 msgid "Single" msgstr "" #: data/config_window.ui.h:25 msgid "Summary" msgstr "" po/gl.po0000664000175000017500000001005212072264260012313 0ustar patrickpatrick# Galician translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:15+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Galician \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Sen nome" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(e {0} máis)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Ten {0} novas mensaxes." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Ten unha nova mensaxe." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "Marcar como lido" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Activado" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Nome" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Eliminar esta conta:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Conta de correo-e" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Servidor:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Contrasinal:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Usuario:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Nome da conta:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configuración de Mailnag" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Contas" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Son de notificación para novas mensaxes" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Intervalo de comprobación:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "minutos" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "" #: data/config_window.ui.h:8 msgid "General" msgstr "Xeral" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Activar filtro antispam" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Filtro de spam" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" #: data/config_window.ui.h:19 msgid "Events" msgstr "Actividades" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "Sobre" #: data/config_window.ui.h:24 msgid "Single" msgstr "" #: data/config_window.ui.h:25 msgid "Summary" msgstr "Resumo" po/zh_CN.po0000664000175000017500000001107312072264260012716 0ustar patrickpatrick# Chinese (Simplified) translation for mailnag # Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2012. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:02+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Chinese (Simplified) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "未命名" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "( 还有 {0} 条)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "您有 {0} 封新邮件。" #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "您有一封新邮件" #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "标记为已读" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "无主题" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "可选" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "已启用" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "名称" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "删除该帐户:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "邮件账户" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "端口:" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "服务器:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "密码:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "用户名:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "账户名称:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "启用 Push-IMAP" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "账户类型:" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "启用 SSL 加密" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag 配置" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "帐户" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "新邮件到达时播放声音" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "登陆时自动启动 Mailnag" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "检查间隔:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "分钟" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "提示内容:" #: data/config_window.ui.h:8 msgid "General" msgstr "基本设置" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "启用垃圾邮件过滤" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "垃圾邮件过滤" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "检查邮件时" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "下列脚本会在每次检查新的邮件时执行。\n" "Mailnag 会把新邮件的总数通知给该脚本,\n" "并告知 \"<sender> <subject>\" 信息。" #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "邮件到达时" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "下列脚本会在接收到新的邮件时执行。\n" "Mailnag 会把新邮件的总数通知给该脚本,\n" "并告知 \"<sender> <subject>\" 信息。" #: data/config_window.ui.h:19 msgid "Events" msgstr "事件" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "关于" #: data/config_window.ui.h:24 msgid "Single" msgstr "单一" #: data/config_window.ui.h:25 msgid "Summary" msgstr "摘要" po/eu.po0000664000175000017500000001146312072264260012331 0ustar patrickpatrick# Basque translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 16:56+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Basque \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Izengabea" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(eta {0} gehiago)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "{0} posta berri dituzu." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Posta berri bat duzu." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "Markatu irakurrita gisa" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "Gairik ez" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "aukerakoa" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Gaituta" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Izena" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Ezabatu kontu hau:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Posta-kontua" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "Ataka:" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Zerbitzaria:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Pasahitza:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Erabiltzailea:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Kontu-izena:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "Karpetak:" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "Gaitu Push-IMAP" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "Kontu-mota:" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "Gaitu SSL zifratzea" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag-en konfigurazioa" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Kontuak" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Erreproduzitu soinua mezu berriak daudenean" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Abiarazi Mailnag automatikoki" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Egiaztatze-tartea:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "minutu" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "Jakinarazpen-modua:" #: data/config_window.ui.h:8 msgid "General" msgstr "Orokorra" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Gaitu spam iragazkia" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Spam-iragazkia" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "Posta egiaztatzean" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Hurrengo script-a posta dagoen ala ez egiaztatzen den bakoitzean exekutatuko " "da.\n" "Mailnag-ek posta berrien kopuru osoa script honi pasatzen dio,\n" " \"<sender> <subject>\" bikoteekin lagunduta." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "Posta iristean" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Hurrengo script-a posta berriak iristen diren bakoitzean exekutatuko da.\n" "Mailnag-ek posta berrien kopuru osoa script honi pasatzen dio,\n" " \"<sender> <subject>\" bikoteekin lagunduta." #: data/config_window.ui.h:19 msgid "Events" msgstr "Gertaerak" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" "Mailnag - Posta-" "jakinarazpenak GNOME 3n\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "eta laguntzaileak." #: data/config_window.ui.h:23 msgid "About" msgstr "Honi buruz" #: data/config_window.ui.h:24 msgid "Single" msgstr "Banaka" #: data/config_window.ui.h:25 msgid "Summary" msgstr "Laburpena" po/ko.po0000664000175000017500000001110312072264260012320 0ustar patrickpatrick# Korean translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:39+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Korean \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "이름 없음" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(그리고 {0}개 더)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "{0}개의 새 메일이 있습니다." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "한 개의 새 메일이 있습니다." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "활성화 됨" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "이름" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "이 계정을 지웁니다:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "메일 계정" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "서버:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "암호:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "사용자:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "계정이름:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag 설정" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "계정들" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "새 메일이 오면 소리 내기" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Mailnag을 자동으로 시작하기" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "확인 간격:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "분" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "" #: data/config_window.ui.h:8 msgid "General" msgstr "일반" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "스팸필터를 활성화하기" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "스팸필터" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "메일 확인시" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "다음 스크립트는 매 메일 확인마다 실행됩니다.\n" "Mailnag은 이 스크립트에 새 메일의 총 갯수를 전달합니다.\n" "그리고 \"<sender> <subject>\" 쌍들이 뒤따릅니다." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "메일 도착시" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "다음 스크립트는 새 메일이 도착할 때마다 실행됩니다.\n" "Mailnag은 이 스크립트에 새 메일의 총 갯수를 전달합니다.\n" "그리고 \"<sender> <subject>\" 쌍들이 뒤따릅니다." #: data/config_window.ui.h:19 msgid "Events" msgstr "사건" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "정보 보기" #: data/config_window.ui.h:24 msgid "Single" msgstr "" #: data/config_window.ui.h:25 msgid "Summary" msgstr "" po/ro.po0000664000175000017500000001070012072264260012331 0ustar patrickpatrick# Romanian translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:54+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Romanian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Nedenumit" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(și {0} altele)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Ai {0} mesaje noi." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Ai un mesaj nou." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Activat" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Nume" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Șterge acest cont:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Cont de email" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Server:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Parola:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Utilizator:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configurare Mailnag" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Conturi" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Sunet la primirea de mesaje noi" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Pornește Mailnag automat" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Interval verificare:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "minute" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "" #: data/config_window.ui.h:8 msgid "General" msgstr "General" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Activează filtrul de spam" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Filtru de spam" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "La verificarea de mesaje noi" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Scriptul urmator va fi executat la fiecare verificare de mesaje noi.\n" "Mailnag trimite acestui script numarul total de mesaje noi,\n" "urmat de \"<sender> <subject>\" pairs." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "La primirea de mesaje noi" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Urmatorul script va fi executat cand primești mesaje noi.\n" "Mailnag trimite acestui script numarul total de mesaje noi,\n" "urmat de \"<sender> <subject>\" pairs." #: data/config_window.ui.h:19 msgid "Events" msgstr "Evenimente" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "Despre" #: data/config_window.ui.h:24 msgid "Single" msgstr "" #: data/config_window.ui.h:25 msgid "Summary" msgstr "" po/cs.po0000664000175000017500000001102212072264260012314 0ustar patrickpatrick# Czech translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:08+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Czech \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Nepojmenovaný" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(a {0} dalších)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Máte {0} nových zpráv." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Máte novou zprávu." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Povoleno" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Název" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Odstranit tento účet:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Poštovní účet" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Server:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Heslo:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Uživatel:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Název účtu:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag - Nastavení" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Účty" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Přehrát zvuk při nových zprávách" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Spustit Mailnag automaticky" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Interval mezi kontrolami:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "minut" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "" #: data/config_window.ui.h:8 msgid "General" msgstr "Obecné" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Povolit filtrování SPAMu" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Spamfilter" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "Při kontrole pošty" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Následující skript bude spuštěn během každé kontroly pošty.\n" "Mailnag předá tomuto skriptu celkový počet nových zpráv\n" "následovaný dvojicemi \"<odesílatel> <předmět>\"." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "Při doručení pošty" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Následující skript bude spuštěn kdykoliv příjde nová zpráva.\n" "Mailnag předá tomuto skriptu celkový počet nových zpráv\n" "následovaný dvojicemi \"<odesílatel> <předmět>\"." #: data/config_window.ui.h:19 msgid "Events" msgstr "Události" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "O aplikaci" #: data/config_window.ui.h:24 msgid "Single" msgstr "" #: data/config_window.ui.h:25 msgid "Summary" msgstr "" po/sr.po0000664000175000017500000001201712072264260012340 0ustar patrickpatrick# Serbian translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:59+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Serbian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Неименовано" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(и још {0})" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "\"Имате {0} нове поруке." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Имате нову пошту." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Укључено" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Назив" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Обриши ове налоге:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Налог поште" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Сервер:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Лозинка:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Корисник:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Назив налога:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Подешавања Поштарка" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Налози" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Звучно обавести када пристигне нова пошта" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Покрени Поштарка аутоматски" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Интервал провере:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "минута" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "" #: data/config_window.ui.h:8 msgid "General" msgstr "Опште" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Укључи филтер спама" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Филтер спама" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "Приликом провере поште" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Следећа скрипта ће бити извршена при сваком засебном проверавању\n" "поште. Поштарко скрипти прослеђује укупан број нових порука,\n" "за којим следи пар \"<пошиљалац> <тема>\"." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "Приликом пристизања поште" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Следећа скрипта ће бити извршена при сваком пристизању нове\n" "поште. Поштарко скрипти прослеђује укупан број нових порука,\n" "за којим следи пар \"<пошиљалац> <тема>\"." #: data/config_window.ui.h:19 msgid "Events" msgstr "Догађаји" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "О програму" #: data/config_window.ui.h:24 msgid "Single" msgstr "" #: data/config_window.ui.h:25 msgid "Summary" msgstr "" po/pt.po0000664000175000017500000001123312072264260012336 0ustar patrickpatrick# Portuguese translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:51+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Sem nome" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(e mais {0})" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Você tem {0} emails novos." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Você tem um email novo." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "Marcar como lido" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "Sem assunto" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "opcional" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Ativado" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Nome" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Apagar esta conta:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Conta de Correio" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "Porta:" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Servidor:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Password:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Utilizador:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Nome da Conta:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "Pastas:" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "Ativar Push-IMAP" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "Tipo de conta:" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "Ativar encriptação SSL" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configuração Mailnag" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Contas" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Tocar som quando receber um novo email" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Iniciar Mailnag automaticamente" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Intervalo de verificação:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "minutos" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "Modo de notificação:" #: data/config_window.ui.h:8 msgid "General" msgstr "Geral" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Ativar filtro de spam" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Filtro de Spam" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "Ao verificar email" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "O script abaixo será executado cada vez que for a feita a verificação de " "correio.\n" "Mailnag passa o número total de novos emails para esse script,\n" "seguido por pares de \"<origem> <assunto>\"." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "Ao receber email" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "O script abaixo será executado sempre que um novo email seja recebido.\n" "Mailnag passa o número total de novos emails para esse script,\n" "seguido por pares de \"<origem> <assunto>\"." #: data/config_window.ui.h:19 msgid "Events" msgstr "Eventos" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "Sobre" #: data/config_window.ui.h:24 msgid "Single" msgstr "Único" #: data/config_window.ui.h:25 msgid "Summary" msgstr "Sumário" po/it.po0000664000175000017500000001150612072264260012332 0ustar patrickpatrick# Italian translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:22+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Italian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Senza nome" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(e altri {0})" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Hai {0} nuovi messaggi email." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Hai un nuovo messaggio email." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "Contrassegna come \\\"già letto\\\"" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "Nessun oggetto" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "opzionale" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Attivo" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Nome" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Eliminare questo account:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Account email" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "Porta:" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Server:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Password:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Nome utente:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Nome dell'account:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "Cartelle:" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "Attiva il protocollo Push-IMAP" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "Tipo di account:" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "Attiva il protocollo crittografico SSL" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configurazione di Mailnag" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Account" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Riproduci un suono all'arrivo di nuovi messaggi email" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Avvia Mailnag automaticamente" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Controlla la casella email ogni" #: data/config_window.ui.h:6 msgid "minutes" msgstr "minuti" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "Modalità di notifica:" #: data/config_window.ui.h:8 msgid "General" msgstr "Impostazioni generali" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Abilita il filtro anti-spam" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Filtro anti-spam" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "Al controllo della casella email" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Lo script seguente sarà eseguito ad ogni controllo della casella di posta " "elettronica.\n" "Mailnag passa a questo script il numero di nuovi messaggi email,\n" "seguito da coppie \"<mittente> <oggetto>\"." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "All'arrivo di un nuovo messaggio email" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Lo script seguente sarà eseguito all'arrivo di ogni nuovo messaggio email.\n" "Mailnag passa a questo script il numero di nuovi messaggi email,\n" "seguito da coppie \"<mittente> <oggetto>\"." #: data/config_window.ui.h:19 msgid "Events" msgstr "Eventi" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "Informazioni" #: data/config_window.ui.h:24 msgid "Single" msgstr "Messaggio singolo" #: data/config_window.ui.h:25 msgid "Summary" msgstr "Riepilogo" po/hr.po0000664000175000017500000001120512072264260012323 0ustar patrickpatrick# Croatian translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:05+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Croatian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Neimenovano" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(i još {0})" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Imate {0} novih poruka." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Imate novu poruku." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Omogućeno" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Ime" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Izbriši ovaj korisnički račun:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "E-mail korisnički račun" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Poslužitelj:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Lozinka" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Korisnik:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Naziv korisničkog računa:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag Konfiguracija" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Korisnički računi" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Izvedi zvuk prilikom primitka nove e-mail poruke" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Pokreni Mailnag automatski" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Period provjere:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "minute" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "" #: data/config_window.ui.h:8 msgid "General" msgstr "Općenito" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Omogući filter neželjenih (SPAM) poruka" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Filter neželjenih (SPAM) poruka" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "Prilikom dohvata novih e-mail poruka" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Sljedeća skripta će biti izvedena prilikom svakog dohvata novih e-mail " "poruka.\n" "Mailnag skripti kao argument predaje konačan broj novih poruka,\n" "koje slijede parovi \"<sender> <subject>\"." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "Prilikom primitka e-mail poruke" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Sljedeća skripta će biti izvedena prilikom primitka novih e-mail poruka.\n" "Mailnag skripti kao argument predaje konačan broj novih poruka,\n" "koje slijede parovi \"<sender> <subject>\"." #: data/config_window.ui.h:19 msgid "Events" msgstr "Događaji" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "O aplikaciji" #: data/config_window.ui.h:24 msgid "Single" msgstr "" #: data/config_window.ui.h:25 msgid "Summary" msgstr "" po/ru.po0000664000175000017500000001254412072264260012347 0ustar patrickpatrick# Russian translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:57+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Russian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Без названия" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(и ещё {0})" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "У вас {0} новых писем." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "У вас новое письмо." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "Отметить как прочитанное" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "Без темы" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "необязательно" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Включено" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Название" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Удалить эту учётную запись:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Учётная запись почты" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "Порт:" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Сервер:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Пароль:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Владелец:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Название учётной записи:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "Папки:" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "Включить Push-IMAP" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "Тип учётной записи:" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "Включить SSL шифрование" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Настройки Mailnag" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Аккаунты" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Проигрывать мелодию при получении нового сообщения" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Запускать Mailnag автоматически" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Интервал проверки:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "минут" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "Режим уведомлений:" #: data/config_window.ui.h:8 msgid "General" msgstr "Общие" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Включить спам-фильтр" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Спам-фильтр" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "При проверке почты" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Этот скрипт будет выполняться при каждой проверке почты.\n" "Mailnag передаёт общее количество новых писем этому скрипту,\n" "добавляя в конец пары вида \"<отправитель> <тема>\"." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "При получении писем" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Этот скрипт будет выполняться при получении новых писем.\n" "Mailnag передаёт общее количество новых писем этому скрипту,\n" "добавляя в конец пары вида \"<отправитель> <тема>\"." #: data/config_window.ui.h:19 msgid "Events" msgstr "События" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "О Mailnag" #: data/config_window.ui.h:24 msgid "Single" msgstr "Одиночный" #: data/config_window.ui.h:25 msgid "Summary" msgstr "Сводная информация" po/ja.po0000664000175000017500000001144712072264260012314 0ustar patrickpatrick# Japanese translation for mailnag # Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 # This file is distributed under the same license as the mailnag package. # Akihiro Tsukada , 2012. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:35+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Japanese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "無記名" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(他 {0} 通)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "{0} 通の新着メールがあります。" #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "メールが来ました。" #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "確認済" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "件名無し" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "省略可" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "使用する" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "名前" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "このアカウントを削除:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "メールアカウント" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "ポート番号:" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "サーバー:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "パスワード:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "ユーザー:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "アカウントの名前:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "フォルダ:" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "Push-IMAP を使う" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "アカウントの種類:" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "SSL暗号化を使う" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag 設定" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "アカウント" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "メールが新着したら音を鳴らす" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Mailnagを自動的に起動する" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "チェック間隔:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "分" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "通知モード:" #: data/config_window.ui.h:8 msgid "General" msgstr "一般" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "スパムフィルターを使う" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "スパムフィルター" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "メールチェック時に" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "メールチェックの度に、このスクリプトを実行します。\n" "新着メールの総数と、\"<送信者> <件名>\" のペアが\n" "Mailnagからこのスクリプトへ渡されます。" #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "メール到着時に" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "メール新着の度に、このスクリプトを実行します。\n" "新着メールの総数と、\"<送信者> <件名>\" のペアが\n" "Mailnagからこのスクリプトへ渡されます。" #: data/config_window.ui.h:19 msgid "Events" msgstr "イベント" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "About" #: data/config_window.ui.h:24 msgid "Single" msgstr "一通毎" #: data/config_window.ui.h:25 msgid "Summary" msgstr "サマリー" po/de.po0000664000175000017500000001145212072264260012306 0ustar patrickpatrick# German translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 16:53+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: German \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Unbenannt" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(und {0} weitere)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Sie haben {0} neue Emails." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Sie haben eine neue Email." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "Als gelesen markieren" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "Kein Betreff" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "optional" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Aktiviert" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Name" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Dieses Konto löschen:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Mail-Konto" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "Port :" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Server:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Passwort:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Benutzer:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Kontoname:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "Ordner:" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "Push-IMAP aktivieren" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "Kontotyp:" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "SSL-Verschlüsselung aktivieren" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag-Konfiguration" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Konten" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Spiele einen Sound bei neuen Emails ab" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Mailnag automatisch starten" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Prüfintervall:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "Minuten" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "Benachrichtigungsmodus:" #: data/config_window.ui.h:8 msgid "General" msgstr "Allgemein" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Spamfilter aktivieren" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Spamfilter" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "Bei Mail-Prüfung" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Das folgende Script wird bei jeder Mail-Prüfung ausgeführt.\n" "Mailnag übergibt die Gesamtanzahl der neuen Mails an dieses Script,\n" "gefolgt von \"<Sender> <Betreff>\" Paaren." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "Bei Mail-Ankunft" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Dieses Script wird immer dann ausgeführt, wenn neue Mails eintreffen.\n" "Mailnag übergibt die Gesamtanzahl der neuen Mails an dieses Script,\n" "gefolgt von \"<Sender> <Betreff>\" Paaren." #: data/config_window.ui.h:19 msgid "Events" msgstr "Ereignisse" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" "Mailnag - Ein Mail-" "Benachrichtiger für GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "und Mitwirkende." #: data/config_window.ui.h:23 msgid "About" msgstr "Info" #: data/config_window.ui.h:24 msgid "Single" msgstr "Einzeln" #: data/config_window.ui.h:25 msgid "Summary" msgstr "Zusammenfassung" po/mailnag.pot0000664000175000017500000000715212072264260013514 0ustar patrickpatrick# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "" #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "" #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "" #: data/config_window.ui.h:6 msgid "minutes" msgstr "" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "" #: data/config_window.ui.h:8 msgid "General" msgstr "" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" #: data/config_window.ui.h:19 msgid "Events" msgstr "" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "" #: data/config_window.ui.h:24 msgid "Single" msgstr "" #: data/config_window.ui.h:25 msgid "Summary" msgstr "" po/he.po0000664000175000017500000001162112072264260012310 0ustar patrickpatrick# Hebrew translation for mailnag # Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2012. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-22 01:18+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Hebrew \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "ללא שם" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(ועוד {0})" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "יש לך {0} הודעות דוא\"ל חדשות." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "יש לך הודעת דוא\"ל חדשה." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "סימון כנקרא" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "ללא נושא" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "אופציונאלי" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "מאופשר" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "שם" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "מחיקת החשבון:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "חשבון דוא\"ל" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "יציאה:" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "שרת:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "סיסמא:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "משתמש:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "שם חשבון:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "איפשור דחיפת IMAP" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "סוג חשבון:" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "איפשור הצפנת SSL" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "הגדרות התוכנה" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "חשבונות" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "השמעת צליל עם קבלת דוא\"ל חדש" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "הפעלה אוטומטית של התוכנה" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "תדירות בדיקה:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "דקות" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "אופן התראות:" #: data/config_window.ui.h:8 msgid "General" msgstr "כללי" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "איפשור מסנן דואר זבל" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "מסנן דואר זבל" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "בזמן בדיקת דוא\"ל" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "הסקריפט הבא יופעל בכל בדיקת דוא\"ל.\n" "Mailnag מעביר את מספר הודעות הדוא\"ל החדשות לסקריפט,\n" "ולאחר מכן זוגות של \"<שולח> <נושא>\"." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "עם קבלת דוא\"ל" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "הסקריפט הבא יופעל כאשר מגיע דוא\"ל חדש.\n" "Mailnag מעביר את מספר הודעות הדוא\"ל החדשות לסקריפט,\n" "ולאחר מכן זוגות של \"<שולח> <נושא>\"." #: data/config_window.ui.h:19 msgid "Events" msgstr "אירועים" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "אודות" #: data/config_window.ui.h:24 msgid "Single" msgstr "יחיד" #: data/config_window.ui.h:25 msgid "Summary" msgstr "תקציר" po/es.po0000664000175000017500000001123712072264260012326 0ustar patrickpatrick# Spanish translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 18:01+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Sin nombre" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(y {0} más)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Tiene {0} mensajes nuevos." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Tiene un mensaje nuevo." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "Marcar como leído" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "Sin asunto" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "opcional" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Activado" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Nombre" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Eliminar esta cuenta:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Cuenta de correo" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "Puerto:" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Servidor:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Contraseña:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Usuario:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Nombre de la cuenta:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "Carpetas:" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "Activar Push-IMAP" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "Tipo de cuenta:" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "Activar encriptación SSL" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configuración de Mailnag" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Cuentas" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Sonido de notificación para nuevos correos" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Iniciar Mailnag automáticamente" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Intervalo de comprobación:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "minutos" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "Modo de notificación:" #: data/config_window.ui.h:8 msgid "General" msgstr "General" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Activar filtro antispam" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Filtro de spam" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "Al comprobar correo" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "El siguiente script será ejecutado en cada comprobación de correo\n" "Mailnag pasa la cuenta total de nuevos correos a este script,\n" "seguido por parejas de \"<sender> <subject>\" ." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "A la llegada de correo" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "El siguiente script será ejecutado para cada correo que entre.\n" "Mailnag pasa la cuenta total de correos nuevos al script,\n" "seguido por pares de \"<sender> <subject>\"." #: data/config_window.ui.h:19 msgid "Events" msgstr "Eventos" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "Acerca de" #: data/config_window.ui.h:24 msgid "Single" msgstr "Único" #: data/config_window.ui.h:25 msgid "Summary" msgstr "Resumen" po/uk.po0000664000175000017500000001227612072264260012342 0ustar patrickpatrick# Ukrainian translation for mailnag # This file is distributed under the same license as the mailnag package. # Rax G , 2012. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 18:03+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Ukrainian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Без імені" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(і ще {0})" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Ви отримали {0} нових повідомлень." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Ви отримали пошту." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "Позначити як прочитане" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "Без теми" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "(необов’язково)" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Увімкнено" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Ім’я" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Поштовий обліковий запис" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "Порт" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Сервер:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Пароль:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Користувач:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Назва запису:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "Теки:" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "Увімкнути Push-IMAP" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "Протокол:" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "Увімкнути SSL-шифрування" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Налаштування сповіщень про пошту" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Облікові записи" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Відтворювати звуковий сигнал при отриманні пошти" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Запускати сповіщувач автоматично" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Інтервал перевірки:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "хв" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "Режим сповіщення:" #: data/config_window.ui.h:8 msgid "General" msgstr "Сповіщення" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Задіяти спам-фільтр" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Фільтр" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "При перевірці скриньки" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Скрипт запускатиметься під час кожної перевірки скриньки.\n" "«Mailnag» передаватиме йому загальну кількість повідомлень\n" "та пари «<адресант> <тема>»." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "При появі нової пошти" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "Скрипт запускатиметься щоразу при появі нової пошти.\n" "«Mailnag» передаватиме йому загальну кількість повідомлень\n" "та пари «<адресант> <тема>»." #: data/config_window.ui.h:19 msgid "Events" msgstr "Запуск команд" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "Про Mailnag" #: data/config_window.ui.h:24 msgid "Single" msgstr "По одному" #: data/config_window.ui.h:25 msgid "Summary" msgstr "Підсумок" po/pt_BR.po0000664000175000017500000001125212072264260012722 0ustar patrickpatrick# Brazilian Portuguese translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 16:59+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Brazilian Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Sem nome" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(e mais {0})" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Você tem {0} novos emails." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Você tem um novo email." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "Marcar como lido" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "Sem assunto" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "opcional" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Ativado" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Nome" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Apagar esta conta:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Conta de email" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "Porta:" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Servidor:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Senha:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Usuário:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Nome da conta:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "Pastas:" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "Habilitar Push-IMAP" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "Tipo de conta:" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "Habilitar criptografia SSL" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configuração do Mailnag" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Contas" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "Reproduzir sons ao chegar novos emails" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Iniciar Mailnag automaticamente" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Intervalo de verificação:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "minutos" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "Modo de notificação:" #: data/config_window.ui.h:8 msgid "General" msgstr "Geral" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "Ativar filtro de spam" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "Filtro de Spam" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "Ao checar email" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "O script abaixo será executado toda vez que for executada a verificação de " "email.\n" "Mailnag passará o número total de novos emails para esse script,\n" "seguindo por \"<origem> <assunto>\"." #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "Na chegada do email" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" "O script abaixo será executado toda vez que um novo email for recebido.\n" "Mailnag passará o número total de novos email para esse script,\n" "seguido por y \"<origem> <assunto>\"." #: data/config_window.ui.h:19 msgid "Events" msgstr "Eventos" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "Sobre" #: data/config_window.ui.h:24 msgid "Single" msgstr "Único" #: data/config_window.ui.h:25 msgid "Summary" msgstr "Sumário" po/pl.po0000664000175000017500000001011012072264260012317 0ustar patrickpatrick# Polish translation for mailnag # Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2011. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2012-12-20 21:44+0100\n" "PO-Revision-Date: 2012-12-21 17:47+0000\n" "Last-Translator: Patrick Ulbrich \n" "Language-Team: Polish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-12-22 13:52+0000\n" "X-Generator: Launchpad (build 16378)\n" #: Mailnag/common/account.py:42 msgid "Unnamed" msgstr "Anonimowy" #: Mailnag/daemon/mailchecker.py:131 msgid "(and {0} more)" msgstr "(i {0} więcej)" #: Mailnag/daemon/mailchecker.py:134 msgid "You have {0} new mails." msgstr "Masz {0} nowych wiadomości." #: Mailnag/daemon/mailchecker.py:136 msgid "You have a new mail." msgstr "Masz nową wiadomość." #: Mailnag/daemon/mailchecker.py:146 msgid "Mark as read" msgstr "Oznacz jako przeczytana" #: Mailnag/daemon/mails.py:111 Mailnag/daemon/mails.py:172 msgid "No subject" msgstr "Brak tematu" #: Mailnag/configuration/accountdialog.py:59 #: Mailnag/configuration/accountdialog.py:60 msgid "optional" msgstr "" #: Mailnag/configuration/configwindow.py:72 msgid "Enabled" msgstr "Włączone" #: Mailnag/configuration/configwindow.py:78 msgid "Name" msgstr "Nazwa" #: Mailnag/configuration/configwindow.py:276 msgid "Delete this account:" msgstr "Usuń to konto:" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "" #: data/account_dialog.ui.h:2 msgid "Port:" msgstr "Port:" #: data/account_dialog.ui.h:3 msgid "Server:" msgstr "Serwer:" #: data/account_dialog.ui.h:4 msgid "Password:" msgstr "Hasło:" #: data/account_dialog.ui.h:5 msgid "User:" msgstr "Użytkownik:" #: data/account_dialog.ui.h:6 msgid "Accountname:" msgstr "Nazwa konta:" #: data/account_dialog.ui.h:7 msgid "Folders:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Enable Push-IMAP" msgstr "Włącz Push-IMAP" #: data/account_dialog.ui.h:9 msgid "Account type:" msgstr "Typ konta:" #: data/account_dialog.ui.h:10 msgid "Enable SSL encryption" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Konfiguracja Mailnag" #: data/config_window.ui.h:2 msgid "Accounts" msgstr "Konta" #: data/config_window.ui.h:3 msgid "Play sound on new mails" msgstr "" #: data/config_window.ui.h:4 msgid "Start Mailnag automatically" msgstr "Uruchamiaj Mailnag automatycznie" #: data/config_window.ui.h:5 msgid "Check interval:" msgstr "Częstotliwość sprawdzania:" #: data/config_window.ui.h:6 msgid "minutes" msgstr "" #: data/config_window.ui.h:7 msgid "Notification mode:" msgstr "" #: data/config_window.ui.h:8 msgid "General" msgstr "" #: data/config_window.ui.h:9 msgid "Enable spamfilter" msgstr "" #: data/config_window.ui.h:10 msgid "Spamfilter" msgstr "" #: data/config_window.ui.h:11 msgid "On mail check" msgstr "Sprawdź pocztę" #: data/config_window.ui.h:12 msgid "" "The following script will be executed on every single mail check.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" #: data/config_window.ui.h:15 msgid "On mail arrival" msgstr "Po przybyciu poczty" #: data/config_window.ui.h:16 msgid "" "The following script will be executed whenever new mails arrive.\n" "Mailnag passes the total count of new mails to this script,\n" "followed by \"<sender> <subject>\" pairs." msgstr "" #: data/config_window.ui.h:19 msgid "Events" msgstr "Wydarzenia" #: data/config_window.ui.h:20 msgid "" "Mailnag - A mail notifier " "for GNOME 3\n" "Copyright (c) 2011, 2012 Patrick Ulbrich \n" "and contributors." msgstr "" #: data/config_window.ui.h:23 msgid "About" msgstr "O aplikacji" #: data/config_window.ui.h:24 msgid "Single" msgstr "" #: data/config_window.ui.h:25 msgid "Summary" msgstr "Podsumowanie" README.md0000664000175000017500000000477012072264260012224 0ustar patrickpatrick# Mailnag A mail notification daemon for GNOME 3. https://github.com/pulb/mailnag Mailnag checks POP3 and IMAP servers for new mail. When it finds new messsages, it creates a GNOME-Shell notification that mentions sender and subject. __This project needs contributors!__ [Code](https://github.com/pulb/mailnag) [Bugtracker](https://github.com/pulb/mailnag/issues) [Translations](https://translations.launchpad.net/mailnag) [Wiki](https://github.com/pulb/mailnag/wiki) ## Installation ### Ubuntu Mailnag has an official [Ubuntu PPA](https://launchpad.net/~pulb/+archive/mailnag). Issue the following commands in a terminal to enable the PPA and install Mailnag. sudo add-apt-repository ppa:pulb/mailnag sudo apt-get update sudo apt-get install mailnag As of Ubuntu 13.04 (Raring), Mailnag is also available in the official repos. Run `sudo apt-get install mailnag` in a terminal to install it. ### Debian Mailnag is currently available in Debian unstable. Run `sudo apt-get install mailnag` in a terminal to install it. ### Fedora As of Fedora 17, Mailnag is available in the official Fedora repos. Just run `yum install mailnag` (as root) in a terminal to install the package. ### Generic Tarballs Distribution independent tarball releases are available [here](https://launchpad.net/mailnag/trunk/mailnag-master). Just run `./setup.py install` (as root) to install Mailnag, though make sure the requirements stated below are met. ###### Requirements * python2 (python3 won't work!) * pygobject * gir-notify * gir-gstreamer * gir-glib-2.0 * gir-gnomekeyring-1.0 * python-httplib2 * python-dbus * pyxdg * gettext ## Configuration Run `mailnag_config` to setup Mailnag. Closing the configuration window will start Mailnag automatically. ### Default Mail Client Clicking a mail notification popup will open the default mail client specified in `System Settings -> System Info -> Default Applications`. If you're a webmail (e.g. gmail) user and want your account to be launched in a browser, please install a tool like [gnome-gmail](http://gnome-gmail.sourceforge.net). ### Permanent Notifications GNOME-Shell notifications are visible for a few seconds only before they vanish in GNOME's hidden messaging tray. If you like to have a permanently visible notification counter in your top panel, you probably want to install [this](https://github.com/pulb/shell-message-notifier) GNOME-Shell extension. ## Screenshots ![Screenshot](http://www.shockshit.net/mailnag/screenshots/mailnag_flyer.png) setup.py0000775000175000017500000000707012072264260012456 0ustar patrickpatrick#!/usr/bin/env python # To install Mailnag run this script as root: # ./setup.py install from distutils.core import setup from distutils.cmd import Command from distutils.log import warn, info, error from distutils.command.install_data import install_data from distutils.command.build import build from distutils.sysconfig import get_python_lib import sys import os import subprocess import glob import shutil from Mailnag.common.dist_cfg import PACKAGE_NAME, APP_VERSION # TODO : This hack won't work with --user and --home options PREFIX = '/usr' for arg in sys.argv: if arg.startswith('--prefix='): PREFIX = arg[9:] BUILD_DIR = 'build' for arg in sys.argv: if arg.startswith('--build-base='): BUILD_DIR = arg[13:] BUILD_LOCALE_DIR = os.path.join(BUILD_DIR, 'locale') BUILD_PATCH_DIR = os.path.join(BUILD_DIR, 'patched') INSTALL_LIB_DIR = os.path.join(get_python_lib(prefix=PREFIX), 'Mailnag') class BuildData(build): def run (self): # generate translations try: rc = subprocess.call('./gen_locales ' + BUILD_LOCALE_DIR, shell = True) if (rc != 0): if (rc == 1): err = "MKDIR_ERR" elif (rc == 2): err = "MSGFMT_ERR" else: err = "UNKNOWN_ERR" raise Warning, "gen_locales returned %d (%s)" % (rc, err) except Exception, e: error("Building locales failed.") error("Error: %s" % str(e)) sys.exit(1) # remove patch dir (if existing) shutil.rmtree(BUILD_PATCH_DIR, ignore_errors = True) # copy mailnag source to build dir for patching purposes shutil.copytree('Mailnag/common', os.path.join(BUILD_PATCH_DIR, 'common')) # patch paths self._patch_file('./mailnag', os.path.join(BUILD_PATCH_DIR, 'mailnag'), './Mailnag', INSTALL_LIB_DIR) self._patch_file('./mailnag_config', os.path.join(BUILD_PATCH_DIR, 'mailnag_config'), './Mailnag', INSTALL_LIB_DIR) self._patch_file('./data/mailnag_config.desktop', os.path.join(BUILD_PATCH_DIR, 'mailnag_config.desktop'), '/usr', PREFIX) self._patch_file(os.path.join(BUILD_PATCH_DIR, 'common/dist_cfg.py'), os.path.join(BUILD_PATCH_DIR, 'common/dist_cfg.py'), './locale', os.path.join(PREFIX, 'share/locale')) build.run (self) def _patch_file(self, infile, outfile, orig, replaced): with open(infile, 'r') as f: strn = f.read() strn = strn.replace(orig, replaced) with open(outfile, 'w') as f: f.write(strn) class InstallData(install_data): def run (self): self._add_locale_data() install_data.run (self) def _add_locale_data(self): for root, dirs, files in os.walk(BUILD_LOCALE_DIR): for file in files: src_path = os.path.join(root, file) dst_path = os.path.join('share/locale', os.path.dirname(src_path[len(BUILD_LOCALE_DIR)+1:])) self.data_files.append((dst_path, [src_path])) class Uninstall(Command): def run (self): # TODO pass setup(name=PACKAGE_NAME, version=APP_VERSION, description='Mail notification daemon for GNOME 3', author='Patrick Ulbrich', author_email='zulu99@gmx.net', url='https://github.com/pulb/mailnag', license='GNU GPL3', package_dir = {'Mailnag.common' : os.path.join(BUILD_PATCH_DIR, 'common')}, packages=['Mailnag', 'Mailnag.common', 'Mailnag.configuration', 'Mailnag.daemon'], scripts=[os.path.join(BUILD_PATCH_DIR, 'mailnag'), os.path.join(BUILD_PATCH_DIR, 'mailnag_config')], data_files=[('share/mailnag', glob.glob('data/*.ui')), ('share/mailnag', ['data/mailnag.ogg']), ('share/mailnag', ['data/mailnag.svg']), ('share/applications', [os.path.join(BUILD_PATCH_DIR, 'mailnag_config.desktop')])], cmdclass={'build': BuildData, 'install_data': InstallData, 'uninstall': Uninstall} )