pax_global_header00006660000000000000000000000064124656471720014530gustar00rootroot0000000000000052 comment=b41e0fb650f82c231090c37287a40f719f35ef4f mailnag-1.1.0/000077500000000000000000000000001246564717200131375ustar00rootroot00000000000000mailnag-1.1.0/.gitignore000066400000000000000000000000151246564717200151230ustar00rootroot00000000000000*.pyc locale mailnag-1.1.0/AUTHORS000066400000000000000000000024511246564717200142110ustar00rootroot00000000000000Maintainer: =========== 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 . Code, docs and packaging contributors: ====================================== Amin Bandali Edwin Smulders Leighton Earl Taylor Braun-Jones Thorsten Leemhuis Vincent Cheng Artwork & icon design: ====================== Reda Lazri Translators (launchpad): ======================== Adolfo Jayme Barrientos Alin Andrei AmiG Akihiro Tsukada Asier Sarasua Garmendia Aydın Yakar Bae Taegil Dmitry Shachnev Eugene Marshal Einar Uvsløkk Hromin Hu Meng Isamu715 Ivo Majić javiggvv Jirka Dutka KEIII kristian LEROY Jean-Christophe Lê Trường An Lidinei Lukasz Man from Mars Manuel Xosé Lemos Marcos Lans Marti Bosch Mattia Meneguzzo Мирослав Николић Oleg «Eleidan» Kulyk Patrick Ulbrich Pedro Beja Philippe Poumaroux Piotr Filipek poulp Rafael Neri RapierTG Rax Szymon Nieznański Tobias Bannert u-t vbert Vyacheslav Sharmanov Wolter Hellmund Zeppelinlg 朱涛 mailnag-1.1.0/LICENSE000066400000000000000000000432541246564717200141540ustar00rootroot00000000000000 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. mailnag-1.1.0/Mailnag/000077500000000000000000000000001246564717200145075ustar00rootroot00000000000000mailnag-1.1.0/Mailnag/__init__.py000066400000000000000000000014711246564717200166230ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- 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-1.1.0/Mailnag/common/000077500000000000000000000000001246564717200157775ustar00rootroot00000000000000mailnag-1.1.0/Mailnag/common/__init__.py000066400000000000000000000014711246564717200201130ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- 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-1.1.0/Mailnag/common/accounts.py000066400000000000000000000204441246564717200201740ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # accounts.py # # Copyright 2011 - 2015 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 logging import Mailnag.daemon.imaplib2 as imaplib from Mailnag.common.i18n import _ account_defaults = { 'enabled' : '0', 'name' : '', 'user' : '', 'password' : '', 'server' : '', 'port' : '', 'ssl' : '1', 'imap' : '1', 'idle' : '1', 'folder' : '' } CREDENTIAL_KEY = 'Mailnag password for %s://%s@%s' # # Account class # class Account: def __init__(self, enabled = False, name = _('Unnamed'), user = '', \ password = '', oauth2string = '', server = '', port = '', ssl = True, imap = True, idle = True, folder = '' ): self.enabled = enabled # bool self.name = name self.user = user self.password = password self.oauth2string = oauth2string 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) # Indicates whether the account # holds an active existing connection. # Note: this method only indicates if the # account *holds* (caches) an existing connection. # There may be further, but no longer # associated connections if get_connection() # was called multiple times (with use_existing # set to False). def has_connection(self): if self.imap: return self._has_IMAP_connection() else: return self._has_POP3_connection() def get_id(self): # TODO : this id is not really unique... return str(hash(self.user + self.server + self.folder)) def _has_IMAP_connection(self): return (self._conn != None) and \ (self._conn.state != imaplib.LOGOUT) and \ (not self._conn.Terminate) def _get_IMAP_connection(self, use_existing): # try to reuse existing connection if use_existing and self._has_IMAP_connection(): 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, int(self.port)) else: if self.port == '': conn = imaplib.IMAP4(self.server) else: conn = imaplib.IMAP4(self.server, int(self.port)) if self.oauth2string != '': conn.authenticate('XOAUTH2', lambda x: self.oauth2string) else: conn.login(self.user, self.password) self._conn = conn except: logging.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 _has_POP3_connection(self): return (self._conn != None) and \ ('sock' in self._conn.__dict__) def _get_POP3_connection(self, use_existing): # try to reuse existing connection if use_existing and self._has_POP3_connection(): 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, int(self.port)) else: if self.port == '': conn = poplib.POP3(self.server) else: conn = poplib.POP3(self.server, int(self.port)) conn.getwelcome() conn.user(self.user) conn.pass_(self.password) self._conn = conn except: logging.error("Cannot connect to POP account: '%s'." % self.server) try: if conn != None: conn.quit() except: pass return self._conn # # AccountManager class # class AccountManager: def __init__(self, credentialstore = None): self._accounts = [] self._removed = [] self._credentialstore = credentialstore def __len__(self): return len(self._accounts) def __iter__(self): for acc in self._accounts: yield acc def __contains__(self, item): return (item in self._accounts) def add(self, account): self._accounts.append(account) def remove(self, account): self._accounts.remove(account) self._removed.append(account) def clear(self): for acc in self._accounts: self._removed.append(acc) del self._accounts[:] def to_list(self): # Don't pass a ref to the internal accounts list. # (Accounts must be removed via the remove() method only.) return self._accounts[:] def load_from_cfg(self, cfg, enabled_only = False): del self._accounts[:] del self._removed[:] 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') password = self._get_account_cfg(cfg, section_name, 'password') 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') if self._credentialstore != None: protocol = 'imap' if imap else 'pop' password = self._credentialstore.get(CREDENTIAL_KEY % (protocol, user, server)) acc = Account(enabled, name, user, password, '', server, port, ssl, imap, idle, folder) self._accounts.append(acc) i = i + 1 section_name = "account" + str(i) def save_to_cfg(self, cfg): # Remove all 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) # Delete secrets of removed accounts from the credential store # (it's important to do this before adding accounts, # in case multiple accounts with the same credential key exist). if self._credentialstore != None: for acc in self._removed: protocol = 'imap' if acc.imap else 'pop' # Note: CredentialStore implementations must check if the key acutally exists! self._credentialstore.remove(CREDENTIAL_KEY % (protocol, acc.user, acc.server)) del self._removed[:] # Add accounts i = 1 for acc in self._accounts: if acc.oauth2string != '': logging.warning("Saving of OAuth2 based accounts is not supported. Account '%s' skipped." % acc.name) continue 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, 'password', '') 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) if self._credentialstore != None: protocol = 'imap' if acc.imap else 'pop' self._credentialstore.set(CREDENTIAL_KEY % (protocol, acc.user, acc.server), acc.password) else: cfg.set(section_name, 'password', acc.password) i = i + 1 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-1.1.0/Mailnag/common/config.py000066400000000000000000000032051246564717200176160ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # config.py # # Copyright 2011 - 2015 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 mailnag_defaults = { 'core': { 'poll_interval' : '10', 'imap_idle_timeout' : '10', 'autostart' : '1', 'connectivity_test' : 'auto', 'credentialstore' : 'auto', 'enabled_plugins' : 'dbusplugin, soundplugin, libnotifyplugin' } } 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-1.1.0/Mailnag/common/credentialstore.py000066400000000000000000000106161246564717200215440ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # credentialstore.py # # Copyright 2015 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 hashlib # TODO: Make this class an enum # when Mailnag is ported to python 3 class CredentialStoreType: NONE = 'none' GNOME = 'gnome' # KDE = 'kde' _credentialstoretype = CredentialStoreType.NONE try: from gi.repository import GnomeKeyring _credentialstoretype = CredentialStoreType.GNOME except: pass # # CredentialStore base class # class CredentialStore: _instance = None def set(self, key, secret): pass def get(self, key): pass def remove(self, key): pass @staticmethod def get_default(): if (CredentialStore._instance == None) and (_credentialstoretype != CredentialStoreType.NONE): CredentialStore._instance = SupportedCredentialStores[_credentialstoretype]() return CredentialStore._instance @staticmethod def from_string(strn): cs = None if strn == 'auto': cs = CredentialStore.get_default() elif strn in SupportedCredentialStores: cs = SupportedCredentialStores[strn]() return cs # # GNOME CredentialStore # class GnomeCredentialStore(CredentialStore): def __init__(self): (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 KeyringUnlockException('Failed to unlock default keyring') self._migrate_keyring() def get(self, key): attrs = self._get_attrs(key) result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.GENERIC_SECRET, attrs) if result == GnomeKeyring.Result.OK: return items[0].secret else: return '' def set(self, key, secret): if secret == '': return attrs = self._get_attrs(key) existing_secret = '' result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.GENERIC_SECRET, attrs) if result == GnomeKeyring.Result.OK: existing_secret = items[0].secret if existing_secret != secret: GnomeKeyring.item_create_sync(self._defaultKeyring, \ GnomeKeyring.ItemType.GENERIC_SECRET, key, \ attrs, secret, True) def remove(self, key): attrs = self._get_attrs(key) result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.GENERIC_SECRET, attrs) if result == GnomeKeyring.Result.OK: GnomeKeyring.item_delete_sync(self._defaultKeyring, items[0].item_id) def _get_attrs(self, key): attrs = GnomeKeyring.Attribute.list_new() keyid = hashlib.md5(key.encode('utf-8')).hexdigest() GnomeKeyring.Attribute.list_append_string(attrs, 'source', 'Mailnag') GnomeKeyring.Attribute.list_append_string(attrs, 'api-version', '1.1') GnomeKeyring.Attribute.list_append_string(attrs, 'keyid', keyid) return attrs # Migrates pre Mailnag 1.1 keyring items into the new format def _migrate_keyring(self): attrs = GnomeKeyring.Attribute.list_new() GnomeKeyring.Attribute.list_append_string(attrs, 'application', 'Mailnag') result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.GENERIC_SECRET, attrs) if result == GnomeKeyring.Result.OK: for i in items: result, info = GnomeKeyring.item_get_info_sync(self._defaultKeyring, i.item_id) self.set(info.get_display_name(), i.secret) GnomeKeyring.item_delete_sync(self._defaultKeyring, i.item_id) # # Exception thrown if the GNOME keyring can't be unlocked # class KeyringUnlockException(Exception): def __init__(self, message): Exception.__init__(self, message) # # All supported credential stores # SupportedCredentialStores = { CredentialStoreType.GNOME : GnomeCredentialStore #CredentialStoreType.KDE : KDECredentialStore } mailnag-1.1.0/Mailnag/common/dist_cfg.py000066400000000000000000000042671246564717200201440ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # dist_cfg.py # # Copyright 2012 - 2014 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 = '1.1.0' # 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' # The LIB_DIR variable specifies the root path for the Mailnag python files # (usually you have to make it point to /Mailnag). LIB_DIR = './Mailnag' # The BIN_DIR variable specifies the path for the mailnag start scripts # (usually you have to make it point to '/usr/bin'). BIN_DIR = '.' # DBUS service configuration DBUS_BUS_NAME = 'mailnag.MailnagService' DBUS_OBJ_PATH = '/mailnag/MailnagService' mailnag-1.1.0/Mailnag/common/exceptions.py000066400000000000000000000016521246564717200205360ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # exceptions.py # # Copyright 2014 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. # class InvalidOperationException(Exception): def __init__(self, message): Exception.__init__(self, message) mailnag-1.1.0/Mailnag/common/i18n.py000066400000000000000000000022041246564717200171260ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # i18n.py # # Copyright 2011, 2012, 2014 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 Mailnag.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-1.1.0/Mailnag/common/plugins.py000066400000000000000000000153341246564717200200400ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # plugins.py # # Copyright 2013, 2014 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 imp import inspect import logging from Mailnag.common.config import cfg_folder from Mailnag.common.dist_cfg import LIB_DIR PLUGIN_LIB_PATH = os.path.join(LIB_DIR, 'plugins') PLUGIN_USER_PATH = os.path.join(cfg_folder, 'plugins') PLUGIN_PATHS = [ PLUGIN_LIB_PATH, PLUGIN_USER_PATH ] # # All known hook types. # # TODO : make this class an enum # when Mailnag is ported to python3 class HookTypes: # func signature: # IN: List of loaded accounts # OUT: None ACCOUNTS_LOADED = 'accounts-loaded' # func signature: # IN: None # OUT: None MAIL_CHECK = 'mail-check' # func signature: # IN: new mails, all mails # OUT: None MAILS_ADDED = 'mails-added' # func signature: # IN: remaining mails # OUT: None MAILS_REMOVED = 'mails-removed' # func signature: # IN: all mails # OUT: filtered mails FILTER_MAILS = 'filter-mails' # # Registry class for plugin hooks. # # Registered hook functions must not block the mailnag daemon. # Hook functions with an execution time > 1s should be # implemented non-blocking (i. e. asynchronously). class HookRegistry: def __init__(self): self._hooks = { HookTypes.ACCOUNTS_LOADED : [], HookTypes.MAIL_CHECK : [], HookTypes.MAILS_ADDED : [], HookTypes.MAILS_REMOVED : [], HookTypes.FILTER_MAILS : [] } def register_hook_func(self, hooktype, func): self._hooks[hooktype].append(func) def unregister_hook_func(self, hooktype, func): self._hooks[hooktype].remove(func) def get_hook_funcs(self, hooktype): return self._hooks[hooktype] # Abstract base class for a MailnagController instance # passed to plugins. class MailnagController: # Returns a HookRegistry object. def get_hooks(self): pass # Shuts down the Mailnag process. # May throw an InvalidOperationException. def shutdown(self): pass # Enforces a manual mail check. # May throw an InvalidOperationException. def check_for_mails(self): pass # Marks the mail with specified mail_id as read. # May throw an InvalidOperationException. def mark_mail_as_read(self, mail_id): pass # # Mailnag Plugin base class # class Plugin: def __init__(self): # Plugins shouldn't do anything in the constructor. # They are expected to start living if they are actually # enabled (i.e. in the enable() method). # Plugin data isn't enabled yet and call to methods like # get_mailnag_controller() or get_config(). pass # # Abstract methods, # to be overriden by derived plugin types. # def enable(self): # Plugins are expected to # register all hooks here. raise NotImplementedError def disable(self): # Plugins are expected to # unregister all hooks here, # free all allocated resources, # and terminate threads (if any). raise NotImplementedError def get_manifest(self): # Plugins are expected to # return a tuple of the following form: # (name, description, version, author, mandatory). raise NotImplementedError def get_default_config(self): # Plugins are expected to return a # dictionary with default values. raise NotImplementedError def has_config_ui(self): # Plugins are expected to return True if # they provide a configuration widget, # otherwise they must return False. raise NotImplementedError def get_config_ui(self): # Plugins are expected to # return a GTK widget here. # Return None if the plugin # does not need a config widget. raise NotImplementedError def load_ui_from_config(self, config_ui): # Plugins are expected to # load their config values (get_config()) # in the widget returned by get_config_ui(). raise NotImplementedError def save_ui_to_config(self, config_ui): # Plugins are expected to # save the config values of the widget # returned by get_config_ui() to their config # (get_config()). raise NotImplementedError # # Public methods # def init(self, modname, cfg, mailnag_controller): config = {} # try to load plugin config if cfg.has_section(modname): for name, value in cfg.items(modname): config[name] = value # sync with default config default_config = self.get_default_config() for k, v in default_config.iteritems(): if not config.has_key(k): config[k] = v self._modname = modname self._config = config self._mailnag_controller = mailnag_controller def get_name(self): name = self.get_manifest()[0] return name def get_modname(self): return self._modname def get_config(self): return self._config # # Protected methods # def get_mailnag_controller(self): return self._mailnag_controller # # Static methods # # Note : Plugin instances do not own # a reference to MailnagController object # when instantiated in *config mode*. @staticmethod def load_plugins(cfg, mailnag_controller = None, filter_names = None): plugins = [] plugin_types = Plugin._load_plugin_types() for modname, t in plugin_types: try: if (filter_names == None) or (modname in filter_names): p = t() p.init(modname, cfg, mailnag_controller) plugins.append(p) except: logging.exception("Failed to instantiate plugin '%s'" % modname) return plugins @staticmethod def _load_plugin_types(): plugin_types = [] for path in PLUGIN_PATHS: if not os.path.exists(path): continue for f in os.listdir(path): mod = None modname, ext = os.path.splitext(f) try: if ext.lower() == '.py': if not os.path.exists(os.path.join(path, modname + '.pyc')): mod = imp.load_source(modname, os.path.join(path, f)) elif ext.lower() == '.pyc': mod = imp.load_compiled(modname, os.path.join(path, f)) if mod != None: for t in dir(mod): t = getattr(mod, t) if inspect.isclass(t) and \ (inspect.getmodule(t) == mod) and \ issubclass(t, Plugin): plugin_types.append((modname, t)) except: logging.exception("Error while opening plugin file '%s'" % f) return plugin_types mailnag-1.1.0/Mailnag/common/subproc.py000066400000000000000000000072751246564717200200410ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # subproc.py # # Copyright 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. # import subprocess import threading import logging import time # Note : All functions of this module *are* thread-safe. # Protects access to the proc dictionary. _proc_lock = threading.Lock() # Intended to prevent simultaneous execution of # (start_subprocess, terminate_subprocesses), # (terminate_subprocesses, terminate_subprocesses). _func_lock = threading.Lock() # Dictionary holding popen objects # and associated thread objects. _procs = {} # Starts a subprocess and an associated thread that waits for # the subprocess to terminate (prevents zombie processes). def start_subprocess(args, shell = False, callback = None): def thread(): t = threading.currentThread() try: p = None with _proc_lock: p = _procs[t] retcode = p.wait() if callback != None: callback(retcode) finally: with _proc_lock: del _procs[t] with _func_lock: p = None pid = -1 try: p = subprocess.Popen(args, shell = shell) except: logging.exception('Caught an exception.') if p != None: pid = p.pid t = threading.Thread(target = thread) with _proc_lock: _procs[t] = p t.start() return pid # Terminates all subprocesses that were started by # start_subprocess(). Subprocesses that don't terminate # within the timeframe (seconds) specified by the # timeout argument, are sent a kill signal. def terminate_subprocesses(timeout = 3.0): with _func_lock: threads = [] with _proc_lock: if len(_procs) == 0: return for t, p in _procs.iteritems(): threads.append(t) # Ask all runnig processes to terminate. # This will also terminate associated threads # waiting for p.wait(). # Note : terminate() does not block. try: p.terminate() except: logging.debug('p.terminate() failed') # Start a watchdog thread that will kill # all processes that didn't terminate within seconds. wd = _Watchdog(timeout) wd.start() # Wait for all threads to terminate for t in threads: t.join() wd.stop() if not wd.triggered: logging.info('All subprocesses exited normally.') # Internal Watchdog class class _Watchdog(threading.Thread): def __init__(self, timeout): threading.Thread.__init__(self) self.triggered = False self._timeout = timeout self._event = threading.Event() def run(self): self._event.wait(self._timeout) if not self._event.is_set(): logging.warning('Process termination took too long - watchdog starts killing...') self.triggered = True with _proc_lock: for t, p in _procs.iteritems(): try: # Kill process p and quit the thread # waiting for p to terminate (p.wait()). p.kill() logging.info('Watchdog killed process %s' % p.pid) except: logging.debug('p.kill() failed') def stop(self): # Abort watchdog thread (may have been triggered already) self._event.set() # Wait for watchdog thread (may be inactive already) self.join() mailnag-1.1.0/Mailnag/common/utils.py000066400000000000000000000052141246564717200175130ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # utils.py # # Copyright 2011 - 2014 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 import os import sys import time import dbus import logging import inspect from Mailnag.common.dist_cfg import PACKAGE_NAME, DBUS_BUS_NAME, DBUS_OBJ_PATH def get_data_paths(): # Add "./data" in workdir for running from builddir data_paths = [] data_paths.append("./data") data_paths.extend(base.load_data_paths(PACKAGE_NAME)) return data_paths def get_data_file(filename): """ Return path to @filename if it exists anywhere in the data paths, else return None """ data_paths = get_data_paths() for direc in data_paths: file_path = os.path.join(direc, filename) if os.path.exists(file_path): return file_path return None def fix_cwd(): # Change into local Mailnag source dir, where paths # in dist_cfg.py point to (e.g. "./locale"). # Only required when running Mailnag locally (wihout installation). main_script_path = os.path.realpath(inspect.stack()[-1][1]) main_script_dir = os.path.dirname(main_script_path) os.chdir(main_script_dir) 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 try_call(f, err_retval = None): try: return f() except: logging.exception('Caught an exception.') return err_retval def shutdown_existing_instance(): bus = dbus.SessionBus() if bus.name_has_owner(DBUS_BUS_NAME): sys.stdout.write('Shutting down existing Mailnag process...') sys.stdout.flush() try: proxy = bus.get_object(DBUS_BUS_NAME, DBUS_OBJ_PATH) shutdown = proxy.get_dbus_method('Shutdown', DBUS_BUS_NAME) shutdown() while bus.name_has_owner(DBUS_BUS_NAME): time.sleep(2) print 'OK' except: print 'FAILED' mailnag-1.1.0/Mailnag/configuration/000077500000000000000000000000001246564717200173565ustar00rootroot00000000000000mailnag-1.1.0/Mailnag/configuration/__init__.py000066400000000000000000000014711246564717200214720ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- 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-1.1.0/Mailnag/configuration/accountdialog.py000066400000000000000000000202071246564717200225450ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # accountdialog.py # # Copyright 2011 - 2014 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, Gtk from Mailnag.common.dist_cfg import PACKAGE_NAME from Mailnag.common.i18n import _ from Mailnag.common.utils import get_data_file IDX_GMAIL = 0 IDX_GMX = 1 IDX_WEB_DE = 2 IDX_YAHOO = 3 IDX_IMAP = 4 IDX_POP3 = 5 PROVIDER_CONFIGS = [ [ 'Gmail', 'imap.gmail.com', '993'], [ 'GMX', 'imap.gmx.net', '993'], [ 'Web.de', 'imap.web.de', '993'], [ 'Yahoo', 'imap.mail.yahoo.com', '993'] ] 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._label_account_name = builder.get_object("label_account_name") 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._label_account_server = builder.get_object("label_account_server") self._entry_account_server = builder.get_object("entry_account_server") self._label_account_port = builder.get_object("label_account_port") 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._fill_account_type_cmb() 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: acctype = self._cmb_account_type.get_active() if (acctype == IDX_POP3) or (acctype == IDX_IMAP): 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 acctype == IDX_POP3: self._acc.imap = False self._acc.folder = '' self._acc.idle = False elif acctype == IDX_IMAP: self._acc.imap = True self._acc.folder = self._entry_account_folder.get_text() self._acc.idle = self._chk_account_push.get_active() else: # known provider (imap only) self._acc.name = self._entry_account_user.get_text() self._acc.user = self._entry_account_user.get_text() self._acc.password = self._entry_account_password.get_text() self._acc.ssl = True self._acc.imap = True self._acc.folder = self._entry_account_folder.get_text() self._acc.idle = not self._has_multiple_folders() if acctype < len(PROVIDER_CONFIGS): p = PROVIDER_CONFIGS[acctype] if not (p[0].lower() in self._acc.name.lower()): self._acc.name += (' (%s)' % p[0]) self._acc.server = p[1] self._acc.port = p[2] else: raise Exception('Unknown account type') self._window.destroy() return res def get_account(self): return self._acc def _fill_account_type_cmb(self): # fill acount type cmb for p in PROVIDER_CONFIGS: self._cmb_account_type.append_text(p[0]) self._cmb_account_type.append_text(_("Other (IMAP)")) self._cmb_account_type.append_text(_("Other (POP3)")) # select account type if len(self._acc.server) == 0: # default to Gmail when creating new accounts self._cmb_account_type.set_active(IDX_GMAIL) else: i = 0 idx = -1 for p in PROVIDER_CONFIGS: if p[1] == self._acc.server: idx = i break i+=1 if idx >= 0: self._cmb_account_type.set_active(idx) else: self._cmb_account_type.set_active(IDX_IMAP if self._acc.imap else IDX_POP3) def _has_multiple_folders(self): return ("," in self._entry_account_folder.get_text()) 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 self._has_multiple_folders(): self._chk_account_push.set_active(False) self._chk_account_push.set_sensitive(False) else: self._chk_account_push.set_sensitive(True) else: # validate acctype = self._cmb_account_type.get_active() if (acctype == IDX_POP3) or (acctype == IDX_IMAP): 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 else: # known provider ok = len(self._entry_account_user.get_text()) > 0 and \ len(self._entry_account_password.get_text()) > 0 self._button_save.set_sensitive(ok) def _on_cmb_account_type_changed(self, widget): acctype = self._cmb_account_type.get_active() if acctype == IDX_POP3: self._label_account_name.set_visible(True) self._entry_account_name.set_visible(True) self._label_account_server.set_visible(True) self._entry_account_server.set_visible(True) self._label_account_port.set_visible(True) self._entry_account_port.set_visible(True) self._label_account_folder.set_visible(False) self._entry_account_folder.set_visible(False) self._chk_account_push.set_visible(False) self._chk_account_ssl.set_visible(True) elif acctype == IDX_IMAP: self._label_account_name.set_visible(True) self._entry_account_name.set_visible(True) self._label_account_server.set_visible(True) self._entry_account_server.set_visible(True) self._label_account_port.set_visible(True) self._entry_account_port.set_visible(True) self._label_account_folder.set_visible(True) self._entry_account_folder.set_visible(True) self._chk_account_push.set_visible(True) self._chk_account_ssl.set_visible(True) else: # known provider (imap only) self._label_account_name.set_visible(False) self._entry_account_name.set_visible(False) self._label_account_server.set_visible(False) self._entry_account_server.set_visible(False) self._label_account_port.set_visible(False) self._entry_account_port.set_visible(False) self._label_account_folder.set_visible(True) self._entry_account_folder.set_visible(True) self._chk_account_push.set_visible(False) self._chk_account_ssl.set_visible(False) mailnag-1.1.0/Mailnag/configuration/configwindow.py000066400000000000000000000324661246564717200224400ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # configwindow.py # # Copyright 2011 - 2015 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, Gdk, Gtk, GObject from Mailnag.common.dist_cfg import PACKAGE_NAME, APP_VERSION, BIN_DIR from Mailnag.common.i18n import _ from Mailnag.common.utils import get_data_file, get_data_paths from Mailnag.common.config import read_cfg, write_cfg from Mailnag.common.accounts import Account, AccountManager from Mailnag.common.credentialstore import CredentialStore from Mailnag.common.plugins import Plugin from Mailnag.configuration.accountdialog import AccountDialog from Mailnag.configuration.plugindialog import PluginDialog 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_page_toggled" : self._on_btn_page_toggled, \ "btn_add_account_clicked" : self._on_btn_add_account_clicked, \ "btn_edit_account_clicked" : self._on_btn_edit_account_clicked, \ "btn_remove_account_clicked" : self._on_btn_remove_account_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, \ "btn_edit_plugin_clicked" : self._on_btn_edit_plugin_clicked, \ "treeview_plugins_row_activated" : self._on_treeview_plugins_row_activated, \ "treeview_plugins_cursor_changed" : self._on_treeview_plugins_cursor_changed, \ }) # Add icons in alternative data paths (e.g. ./data/icons) # to the icon search path in case Mailnag is launched # from a local directory (without installing). icon_theme = Gtk.IconTheme.get_default() for path in get_data_paths(): icon_theme.append_search_path(os.path.join(path, "icons")) self._window = builder.get_object("config_window") self._window.set_icon_name("mailnag") self._load_stylesheet('config_window.css') self._cfg = read_cfg() self.daemon_enabled = False # # toggle buttons / notebook # self._notebook = builder.get_object("notebook") self._box_navigation = builder.get_object("box_navigation") self._box_navigation.get_children()[0].set_active(True) # # general page # # The dimension of the png is expected to be 180x180 px pb = GdkPixbuf.Pixbuf.new_from_file(get_data_file("mailnag.png")) pb = pb.new_subpixbuf(0, 10, 180, 146) # crop whitespace at the bottom self._image_logo = builder.get_object("image_logo") 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._switch_daemon_enabled = builder.get_object("switch_daemon_enabled") # # accounts page # self._accountman = AccountManager(CredentialStore.from_string(self._cfg.get('core', 'credentialstore'))) self._treeview_accounts = builder.get_object("treeview_accounts") self._liststore_accounts = builder.get_object("liststore_accounts") self._button_edit_account = builder.get_object("btn_edit_account") self._button_remove_account = builder.get_object("btn_remove_account") self._button_edit_account.set_sensitive(False) self._button_remove_account.set_sensitive(False) renderer_acc_enabled = Gtk.CellRendererToggle() renderer_acc_enabled.connect("toggled", self._on_account_toggled) column_acc_enabled = Gtk.TreeViewColumn(_('Enabled'), renderer_acc_enabled) column_acc_enabled.add_attribute(renderer_acc_enabled, "active", 1) column_acc_enabled.set_alignment(0.5) self._treeview_accounts.append_column(column_acc_enabled) renderer_acc_name = Gtk.CellRendererText() column_acc_name = Gtk.TreeViewColumn(_('Name'), renderer_acc_name, text = 2) self._treeview_accounts.append_column(column_acc_name) # # plugins page # self._treeview_plugins = builder.get_object("treeview_plugins") self._liststore_plugins = builder.get_object("liststore_plugins") self._button_edit_plugin = builder.get_object("btn_edit_plugin") self._button_edit_plugin.set_sensitive(False) def renderer_plugin_enabled_func(column, cell_renderer, model, iter, data): plugin = model.get_value(iter, 0) name, desc, ver, author, mandatory = plugin.get_manifest() cell_renderer.set_sensitive(not mandatory) renderer_plugin_enabled = Gtk.CellRendererToggle() renderer_plugin_enabled.connect("toggled", self._on_plugin_toggled) column_plugin_enabled = Gtk.TreeViewColumn(_('Enabled'), renderer_plugin_enabled) column_plugin_enabled.add_attribute(renderer_plugin_enabled, "active", 1) column_plugin_enabled.set_alignment(0.5) column_plugin_enabled.set_cell_data_func(renderer_plugin_enabled, renderer_plugin_enabled_func) self._treeview_plugins.append_column(column_plugin_enabled) renderer_plugin_name = Gtk.CellRendererText() column_plugin_name = Gtk.TreeViewColumn(_('Name'), renderer_plugin_name, markup = 2) self._treeview_plugins.append_column(column_plugin_name) # load config self._load_config() self._window.show() def _load_stylesheet(self, stylesheet): provider = Gtk.CssProvider() provider.load_from_path(get_data_file(stylesheet)) Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) def _load_config(self): self._switch_daemon_enabled.set_active(bool(int(self._cfg.get('core', 'autostart')))) self._accountman.load_from_cfg(self._cfg) # load accounts for acc in self._accountman: row = [acc, acc.enabled, acc.name] self._liststore_accounts.append(row) self._select_account_path((0,)) # load plugins enabled_lst = self._cfg.get('core', 'enabled_plugins').split(',') enabled_lst = filter(lambda s: s != '', map(lambda s: s.strip(), enabled_lst)) plugins = Plugin.load_plugins(self._cfg) plugins.sort(key = lambda p : (not p.get_manifest()[4], p.get_manifest()[0])) for plugin in plugins: name, desc, ver, author, mandatory = plugin.get_manifest() enabled = True if (plugin.get_modname() in enabled_lst) or mandatory else False description = '%s (%s)\n%s' % (name, ver, desc) row = [plugin, enabled, description] self._liststore_plugins.append(row) self._select_plugin_path((0,)) def _save_config(self): autostart = self._switch_daemon_enabled.get_active() self._cfg.set('core', 'autostart', int(autostart)) self._accountman.save_to_cfg(self._cfg) enabled_plugins = '' for row in self._liststore_plugins: plugin = row[0] modname = plugin.get_modname() if row[1] == True: if len(enabled_plugins) > 0: enabled_plugins += ', ' enabled_plugins += modname config = plugin.get_config() if len(config) > 0: if not self._cfg.has_section(modname): self._cfg.add_section(modname) for k, v in config.iteritems(): self._cfg.set(modname, k, v) self._cfg.set('core', 'enabled_plugins', enabled_plugins) write_cfg(self._cfg) if autostart: self._create_autostart() else: self._delete_autostart() def _show_confirmation_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 1st column if iter != None: acc = model.get_value(iter, 0) else: acc = None return acc, model, iter def _select_account_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 _get_selected_plugin(self): treeselection = self._treeview_plugins.get_selection() selection = treeselection.get_selected() model, iter = selection # get plugin object from treeviews 1st column if iter != None: plugin = model.get_value(iter, 0) else: plugin = None return plugin, model, iter def _select_plugin_path(self, path): treeselection = self._treeview_plugins.get_selection() treeselection.select_path(path) self._treeview_plugins.grab_focus() def _edit_plugin(self): plugin, model, iter = self._get_selected_plugin() if (iter != None) and plugin.has_config_ui(): d = PluginDialog(self._window, plugin) d.run() def _create_autostart(self): # path to mailnag startscript exec_file = os.path.join(os.path.abspath(BIN_DIR), "mailnag") content = "\n" + \ "[Desktop Entry]\n" + \ "Type=Application\n" + \ "Exec=%s --quiet\n" % exec_file + \ "Icon=mailnag\n" + \ "Hidden=false\n" + \ "NoDisplay=false\n" + \ "X-GNOME-Autostart-enabled=true\n" + \ "Name=Mailnag\n" + \ "Comment=An extensible mail notification daemon\n" 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_btn_page_toggled(self, button): if not button.get_active(): return page = 0 for btn in self._box_navigation.get_children(): if btn == button: self._notebook.set_current_page(page) else: btn.set_active(False) page += 1 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_account_clicked(self, widget): acc = Account(enabled = True, name = '') d = AccountDialog(self._window, acc) if d.run() == 1: self._accountman.add(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_account_clicked(self, widget): self._edit_account() def _on_btn_remove_account_clicked(self, widget): acc, model, iter = self._get_selected_account() if iter != None: if self._show_confirmation_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_account_path(p) # remove from treeview model.remove(iter) # remove from account manager self._accountman.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_account.set_sensitive(len(model) > 0) self._button_remove_account.set_sensitive(len(model) > 0) def _on_liststore_accounts_row_inserted(self, model, path, user_param): self._button_edit_account.set_sensitive(len(model) > 0) self._button_remove_account.set_sensitive(len(model) > 0) def _on_plugin_toggled(self, cell, path): model = self._liststore_plugins iter = model.get_iter(path) self._liststore_plugins.set_value(iter, 1, not cell.get_active()) def _on_btn_edit_plugin_clicked(self, widget): self._edit_plugin() def _on_treeview_plugins_row_activated(self, treeview, path, view_column): self._edit_plugin() def _on_treeview_plugins_cursor_changed(self, treeview): # Workaround for a bug in GTK < 3.8, # see http://permalink.gmane.org/gmane.comp.gnome.svn/694089 if not self._window.get_visible(): return plugin, model, iter = self._get_selected_plugin() if iter != None: self._button_edit_plugin.set_sensitive(plugin.has_config_ui()) def _save_and_quit(self): self._save_config() self.daemon_enabled = self._switch_daemon_enabled.get_active() Gtk.main_quit() def _on_config_window_deleted(self, widget, event): self._save_and_quit() mailnag-1.1.0/Mailnag/configuration/plugindialog.py000066400000000000000000000036031246564717200224100ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # plugindialog.py # # Copyright 2013, 2014 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, Gtk from Mailnag.common.dist_cfg import PACKAGE_NAME from Mailnag.common.utils import get_data_file class PluginDialog: def __init__(self, parent, plugin): self._plugin = plugin builder = Gtk.Builder() builder.set_translation_domain(PACKAGE_NAME) builder.add_from_file(get_data_file("plugin_dialog.ui")) builder.connect_signals({ \ "btn_cancel_clicked" : self._on_btn_cancel_clicked, \ "btn_save_clicked" : self._on_btn_save_clicked \ }) self._window = builder.get_object("plugin_dialog") self._window.set_transient_for(parent) self._vbox = builder.get_object("vbox") def run(self): widget = self._plugin.get_config_ui() if widget != None: self._vbox.pack_start(widget, True, True, 0) widget.show_all() self._plugin.load_ui_from_config(widget) res = self._window.run() if res == 1: if widget != None: self._plugin.save_ui_to_config(widget) self._window.destroy() return res def _on_btn_cancel_clicked(self, widget): pass def _on_btn_save_clicked(self, widget): pass mailnag-1.1.0/Mailnag/daemon/000077500000000000000000000000001246564717200157525ustar00rootroot00000000000000mailnag-1.1.0/Mailnag/daemon/__init__.py000066400000000000000000000014711246564717200200660ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- 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-1.1.0/Mailnag/daemon/conntest.py000066400000000000000000000044561246564717200201720ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # conntest.py # # Copyright 2014 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 dbus PING_TEST_HOST = 'www.google.de' NM_STATE_CONNECTED_GLOBAL = 70 NM_PATH = '/org/freedesktop/NetworkManager' NM_NAME = 'org.freedesktop.NetworkManager' DBUS_PROPS_NAME = 'org.freedesktop.DBus.Properties' # TODO: Add GLib connection mode if GLib > 2.42 is available # (wrap GNetworkmonitor.get_connectivity() into a try/except block) # TODO: Make this class an enum # when Mailnag is ported to python3 class TestModes: AUTO = 0 NETWORKMANAGER = 1 PING = 2 class ConnectivityTest: def __init__(self, testmode): self._testmode = testmode self._nm_is_offline = True bus = dbus.SystemBus() if self._testmode == TestModes.AUTO: if bus.name_has_owner(NM_NAME): self._testmode = TestModes.NETWORKMANAGER else: self._testmode = TestModes.PING if self._testmode == TestModes.NETWORKMANAGER: def state_changed_handler(state): self._nm_is_offline = (state != NM_STATE_CONNECTED_GLOBAL) proxy = bus.get_object(NM_NAME, NM_PATH) # Note: connect requires DBusGMainLoop(set_as_default = True) # and a running main loop. proxy.connect_to_signal('StateChanged', state_changed_handler) iface = dbus.Interface(proxy, DBUS_PROPS_NAME) state = iface.Get(NM_NAME, 'State') self._nm_is_offline = (state != NM_STATE_CONNECTED_GLOBAL) def is_offline(self): if self._testmode == TestModes.NETWORKMANAGER: return self._nm_is_offline else: return (os.system('ping -c1 -W2 %s > /dev/null 2>&1' % PING_TEST_HOST) != 0) mailnag-1.1.0/Mailnag/daemon/idlers.py000066400000000000000000000127661246564717200176220ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # idlers.py # # Copyright 2011 - 2014 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 logging from Mailnag.daemon.imaplib2 import AUTH from Mailnag.common.exceptions import InvalidOperationException class ConnectionException(Exception): def __init__(self, message): Exception.__init__(self, message) # # Idler class # class Idler(object): def __init__(self, account, sync_callback, idle_timeout): 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 self._idle_timeout = idle_timeout # use_existing = True: # connection has been opened in mailnagdaemon.py already (immediate check) self._conn = account.get_connection(use_existing = True) self._disposed = False if self._conn == None: raise ConnectionException( "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 start(self): if self._disposed: raise InvalidOperationException("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: # Exit possible active idle state. # (also calls idle_callback) self._conn.noop() except: pass self._disposed = True logging.info('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 minutes at the latest. # gmail sends keepalive events every 5 minutes. self._conn.idle(callback = self._idle_callback, timeout = 60 * self._idle_timeout) # 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 logging.info("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()): logging.info("Trying to reconnect Idler thread for account '%s'." % self._account.name) self._conn = self._account.get_connection(use_existing = False) if self._conn == None: logging.error("Failed to reconnect Idler thread for account '%s'." % self._account.name) logging.info("Trying to reconnect Idler thread for account '%s' in %s minutes" % (self._account.name, str(self.RECONNECT_RETRY_INTERVAL))) self._wait(60 * self.RECONNECT_RETRY_INTERVAL) # don't hammer the server else: logging.info("Successfully reconnected Idler thread for account '%s'." % self._account.name) 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) # # IdlerRunner class # class IdlerRunner: def __init__(self, accounts, sync_callback, idle_timeout): self._idlerlist = [] self._accounts = accounts self._sync_callback = sync_callback self._idle_timeout = idle_timeout def start(self): for acc in self._accounts: if acc.imap and acc.idle: try: idler = Idler(acc, self._sync_callback, self._idle_timeout) idler.start() self._idlerlist.append(idler) except Exception as ex: logging.error("Error: Failed to create an idler thread for account '%s'" % acc.name) def dispose(self): for idler in self._idlerlist: idler.dispose() mailnag-1.1.0/Mailnag/daemon/imaplib2.py000066400000000000000000002456771246564717200200500ustar00rootroot00000000000000#!/usr/bin/env python2 """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-1.1.0/Mailnag/daemon/mailchecker.py000066400000000000000000000063071246564717200206010ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # mailchecker.py # # Copyright 2011 - 2014 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 threading import logging from Mailnag.common.utils import try_call from Mailnag.common.i18n import _ from Mailnag.common.plugins import HookTypes from Mailnag.daemon.mails import MailSyncer class MailChecker: def __init__(self, cfg, memorizer, hookreg, conntest): self._firstcheck = True # first check after startup self._mailcheck_lock = threading.Lock() self._mailsyncer = MailSyncer(cfg) self._memorizer = memorizer self._hookreg = hookreg self._conntest = conntest self._zero_mails_on_last_check = True def check(self, accounts): # make sure multiple threads (idler and polling thread) # don't check for mails simultaneously. with self._mailcheck_lock: logging.info('Checking %s email account(s).' % len(accounts)) for f in self._hookreg.get_hook_funcs(HookTypes.MAIL_CHECK): try_call( f ) if self._conntest.is_offline(): logging.warning('No internet connection.') return all_mails = self._mailsyncer.sync(accounts) unseen_mails = [] new_mails = [] for mail in all_mails: if self._memorizer.contains(mail.id): # mail was fetched before if self._memorizer.is_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) self._memorizer.sync(all_mails) self._memorizer.save() self._firstcheck = False # apply filter plugin hooks filtered_unseen_mails = unseen_mails for f in self._hookreg.get_hook_funcs(HookTypes.FILTER_MAILS): filtered_unseen_mails = try_call( lambda: f(filtered_unseen_mails), filtered_unseen_mails ) filtered_new_mails = [m for m in new_mails if m in filtered_unseen_mails] if len(filtered_new_mails) > 0: for f in self._hookreg.get_hook_funcs(HookTypes.MAILS_ADDED): try_call( lambda: f(filtered_new_mails, filtered_unseen_mails) ) elif (not self._zero_mails_on_last_check) and (len(filtered_unseen_mails) == 0): # TODO : signal MailsRemoved if not all mails have been removed # (i.e. if mailcount has been decreased) for f in self._hookreg.get_hook_funcs(HookTypes.MAILS_REMOVED): try_call( lambda: f(filtered_unseen_mails) ) self._zero_mails_on_last_check = (filtered_unseen_mails == 0) return mailnag-1.1.0/Mailnag/daemon/mailnagdaemon.py000066400000000000000000000214761246564717200211320ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # mailnagdaemon.py # # Copyright 2014, 2015 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 threading import logging import time from Mailnag.common.accounts import AccountManager from Mailnag.common.credentialstore import CredentialStore from Mailnag.daemon.mailchecker import MailChecker from Mailnag.daemon.mails import Memorizer from Mailnag.daemon.idlers import IdlerRunner from Mailnag.daemon.conntest import ConnectivityTest, TestModes from Mailnag.common.plugins import Plugin, HookRegistry, HookTypes, MailnagController from Mailnag.common.exceptions import InvalidOperationException from Mailnag.common.config import read_cfg from Mailnag.common.utils import try_call testmode_mapping = { 'auto' : TestModes.AUTO, 'networkmanager' : TestModes.NETWORKMANAGER, 'ping' : TestModes.PING } class MailnagDaemon: def __init__(self, fatal_error_handler = None, shutdown_request_handler = None): self._cfg = None self._fatal_error_handler = fatal_error_handler self._shutdown_request_handler = shutdown_request_handler self._plugins = [] self._hookreg = None self._conntest = None self._accounts = None self._mailchecker = None self._start_thread = None self._poll_thread = None self._poll_thread_stop = threading.Event() self._idlrunner = None # Lock ensures that init() and dispose() # are non-reentrant. self._lock = threading.Lock() # Flag indicating complete # daemon initialization. self._initialized = False self._disposed = False # Initializes the daemon and starts checking threads. def init(self): with self._lock: if self._disposed: raise InvalidOperationException("Daemon has been disposed") if self._initialized: raise InvalidOperationException("Daemon has already been initialized") self._cfg = read_cfg() accountman = AccountManager(CredentialStore.from_string(self._cfg.get('core', 'credentialstore'))) accountman.load_from_cfg(self._cfg, enabled_only = True) self._accounts = accountman.to_list() self._hookreg = HookRegistry() self._conntest = ConnectivityTest(testmode_mapping[self._cfg.get('core', 'connectivity_test')]) memorizer = Memorizer() memorizer.load() self._mailchecker = MailChecker(self._cfg, memorizer, self._hookreg, self._conntest) # Note: all code following _load_plugins() should be executed # asynchronously because the dbus plugin requires an active mainloop # (usually started in the programs main function). self._load_plugins(self._cfg, self._hookreg, memorizer) # Start checking for mails asynchronously. self._start_thread = threading.Thread(target = self._start) self._start_thread.start() self._initialized = True def dispose(self): with self._lock: if self._disposed: return # Note: _disposed must be set # before cleaning up resources # (in case an exception occurs) # and before unloading plugins. # Also required by _wait_for_inet_connection(). self._disposed = True # clean up resources if (self._start_thread != None) and (self._start_thread.is_alive()): self._start_thread.join() logging.info('Starter thread exited successfully.') if (self._poll_thread != None) and (self._poll_thread.is_alive()): self._poll_thread_stop.set() self._poll_thread.join() logging.info('Polling thread exited successfully.') if self._idlrunner != None: self._idlrunner.dispose() if self._accounts != None: for acc in self._accounts: if acc.has_connection(): if acc.imap: conn = acc.get_connection(use_existing = True) conn.close() conn.logout() else: conn.quit() self._unload_plugins() def is_initialized(self): return self._initialized def is_disposed(self): return self._disposed # Enforces manual mail checks def check_for_mails(self): # Don't allow mail checks before initialization or # after object disposal. F.i. plugins may not be # loaded/unloaded completely or connections may # have been closed already. self._ensure_valid_state() non_idle_accounts = filter(lambda acc: (not acc.imap) or (acc.imap and not acc.idle), self._accounts) self._mailchecker.check(non_idle_accounts) def _ensure_valid_state(self): if not self._initialized: raise InvalidOperationException( "Daemon has not been initialized") if self._disposed: raise InvalidOperationException( "Daemon has been disposed") def _start(self): try: # Call Accounts-Loaded plugin hooks for f in self._hookreg.get_hook_funcs(HookTypes.ACCOUNTS_LOADED): try_call( lambda: f(self._accounts) ) if not self._wait_for_inet_connection(): return # Immediate check, check *all* accounts try: self._mailchecker.check(self._accounts) except: logging.exception('Caught an exception.') idle_accounts = filter(lambda acc: acc.imap and acc.idle, self._accounts) non_idle_accounts = filter(lambda acc: (not acc.imap) or (acc.imap and not acc.idle), self._accounts) # start polling thread for POP3 accounts and # IMAP accounts without idle support if len(non_idle_accounts) > 0: poll_interval = int(self._cfg.get('core', 'poll_interval')) def poll_func(): try: while True: self._poll_thread_stop.wait(timeout = 60.0 * poll_interval) if self._poll_thread_stop.is_set(): break self._mailchecker.check(non_idle_accounts) except: logging.exception('Caught an exception.') self._poll_thread = threading.Thread(target = poll_func) self._poll_thread.start() # start idler threads for IMAP accounts with idle support if len(idle_accounts) > 0: def sync_func(account): try: self._mailchecker.check([account]) except: logging.exception('Caught an exception.') idle_timeout = int(self._cfg.get('core', 'imap_idle_timeout')) self._idlrunner = IdlerRunner(idle_accounts, sync_func, idle_timeout) self._idlrunner.start() except Exception as ex: logging.exception('Caught an exception.') if self._fatal_error_handler != None: self._fatal_error_handler(ex) def _wait_for_inet_connection(self): if self._conntest.is_offline(): logging.info('Waiting for internet connection...') while True: if self._disposed: return False if not self._conntest.is_offline(): return True # Note: don't sleep too long # (see timeout in mailnag.cleanup()) # ..but also don't sleep to short in case of a ping connection test. time.sleep(3) def _load_plugins(self, cfg, hookreg, memorizer): class MailnagController_Impl(MailnagController): def __init__(self, daemon, memorizer, hookreg, shutdown_request_hdlr): self._daemon = daemon self._memorizer = memorizer self._hookreg = hookreg self._shutdown_request_handler = shutdown_request_hdlr def get_hooks(self): return self._hookreg def shutdown(self): if self._shutdown_request_handler != None: self._shutdown_request_handler() def check_for_mails(self): self._daemon.check_for_mails() def mark_mail_as_read(self, mail_id): # Note: ensure_valid_state() is not really necessary here # (the memorizer object is available in init() and dispose()), # but better be consistent with other daemon methods. self._daemon._ensure_valid_state() self._memorizer.set_to_seen(mail_id) self._memorizer.save() controller = MailnagController_Impl(self, memorizer, hookreg, self._shutdown_request_handler) enabled_lst = cfg.get('core', 'enabled_plugins').split(',') enabled_lst = filter(lambda s: s != '', map(lambda s: s.strip(), enabled_lst)) self._plugins = Plugin.load_plugins(cfg, controller, enabled_lst) for p in self._plugins: try: p.enable() logging.info("Successfully enabled plugin '%s'." % p.get_modname()) except: logging.error("Failed to enable plugin '%s'." % p.get_modname()) def _unload_plugins(self): if len(self._plugins) > 0: err = False for p in self._plugins: try: p.disable() except: err = True logging.error("Failed to disable plugin '%s'." % p.get_modname()) if not err: logging.info('Plugins disabled successfully.') mailnag-1.1.0/Mailnag/daemon/mails.py000066400000000000000000000256551246564717200174460ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # mails.py # # Copyright 2011 - 2014 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 email import os import logging import hashlib from email.header import decode_header from Mailnag.common.i18n import _ from Mailnag.common.config import cfg_folder # # Mail class # class Mail: def __init__(self, datetime, subject, sender, id, account_id): self.datetime = datetime self.subject = subject self.sender = sender self.id = id self.account_id = account_id # # MailCollector class # class MailCollector: def __init__(self, cfg, accounts): self._cfg = cfg self._accounts = accounts def collect_mail(self, sort = True): mail_list = [] mail_ids = {} 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: logging.warning('Folder %s does not exist.', folder) continue if status != 'OK' or None in [d for d in data]: logging.debug('Folder %s in status %s | Data: %s', (folder, status, data)) 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: logging.debug("Couldn't get IMAP message.") continue sender, subject, datetime, msgid = self._get_header(msg) id = self._get_id(msgid, acc, folder, sender, subject, datetime) # Discard mails with identical IDs (caused # by mails with a non-unique fallback ID, # i.e. mails received in the same folder with # identical sender and subject but *no datetime*, # see _get_id()). # Also filter duplicates caused by Gmail labels. if id not in mail_ids: mail_list.append(Mail(datetime, subject, \ sender, id, acc.get_id())) mail_ids[id] = None # 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: logging.debug("Couldn't get POP message.") 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: logging.debug("Couldn't get msg from POP message.") continue sender, subject, datetime, msgid = self._get_header(msg) id = self._get_id(msgid, acc, '', sender, subject, datetime) # Discard mails with identical IDs (caused # by mails with a non-unique fallback ID, # i.e. mails with identical sender and subject # but *no datetime*, see _get_id()). if id not in mail_ids: mail_list.append(Mail(datetime, subject, sender, \ id, acc.get_id())) mail_ids[id] = None # disconnect from Email-Server srv.quit() # sort mails if sort: mail_list = sort_mails(mail_list, sort_desc = True) return mail_list def _get_header(self, msg_dict): try: content = self._get_header_field(msg_dict, 'From') sender = self._format_header_field('sender', content) except: sender = ('', '') try: content = self._get_header_field(msg_dict, 'Subject') except: content = _('No subject') try: subject = self._format_header_field('subject', content) except: subject = '' try: content = self._get_header_field(msg_dict, 'Date') datetime = self._format_header_field('date', content) except: logging.warning('Email date set to zero.') datetime = 0 try: msgid = self._get_header_field(msg_dict, 'Message-ID') except: msgid = '' return (sender, subject, datetime, msgid) def _get_header_field(self, msg_dict, key): if key in msg_dict: value = msg_dict[key] elif key.lower() in msg_dict: value = msg_dict[key.lower()] else: logging.debug("Couldn't get %s from message." % key) raise KeyError return value # format sender, date, subject etc. def _format_header_field(self, field_name, content): if field_name == 'sender': # 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 return (sender_real, sender_addr) if field_name == 'date': # make a 10-tupel (UTC) parsed_date = email.utils.parsedate_tz(content) # convert 10-tupel to seconds incl. timezone shift return email.utils.mktime_tz(parsed_date) if field_name == 'subject': return self._convert(content) # 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 def _get_id(self, msgid, acc, folder, sender, subject, datetime): if len(msgid) > 0: id = hashlib.md5(msgid.encode('utf-8')).hexdigest() else: # Fallback ID. # Note: mails received on the same server, # in the same folder with identical sender and # subject but *no datetime* will have the same hash id, # i.e. only the first mail is notified. # (Should happen very rarely). id = hashlib.md5((acc.server + folder + acc.user + sender[1] + subject + str(datetime)) .encode('utf-8')).hexdigest() return id # # MailSyncer class # class MailSyncer: def __init__(self, cfg): self._cfg = cfg self._mails_by_account = {} self._mail_list = [] def sync(self, accounts): needs_rebuild = False # collect mails from given accounts rcv_lst = MailCollector(self._cfg, accounts).collect_mail(sort = False) # 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 = sort_mails(self._mail_list, sort_desc = True) return self._mail_list # # sort_mails function # def sort_mails(mail_list, sort_desc = False): sort_list = [] for mail in mail_list: sort_list.append([mail.datetime, mail]) # sort asc sort_list.sort() if sort_desc: sort_list.reverse() # recreate mail_list mail_list = [] for mail in sort_list: mail_list.append(mail[1]) return mail_list # # Memorizer class # class Memorizer(dict): def __init__(self): dict.__init__(self) self._changed = False def load(self): self.clear() self._changed = False # load last known messages from mailnag.dat dat_file = os.path.join(cfg_folder, 'mailnag.dat') if os.path.exists(dat_file): with open(dat_file, 'r') as f: for line in f: # remove CR at the end stripedline = line.strip() # get all items from one line in a list: [mailid, seen_flag] pair = stripedline.split(',') # add to dict [id : flag] self[pair[0]] = pair[1] # save mail ids to a file def save(self, force = False): if (not self._changed) and (not force): return if not os.path.exists(cfg_folder): os.makedirs(cfg_folder) dat_file = os.path.join(cfg_folder, 'mailnag.dat') with open(dat_file, 'w') as f: for id, seen_flag in self.items(): line = id + ',' + seen_flag + '\n' f.write(line) self._changed = False def sync(self, mail_list): for m in mail_list: if not m.id in self: # new mail is not yet known to the memorizer self[m.id] = '0' self._changed = True for id in self.keys(): found = False for m in mail_list: if id == m.id: found = True # break inner for loop break if not found: del self[id] self._changed = True # check if mail id is in the memorizer list def contains(self, id): return (id in self) # set seen flag for this email def set_to_seen(self, id): self[id] = '1' self._changed = True def is_unseen(self, id): if id in self: flag = self[id] return (flag == '0') else: return True mailnag-1.1.0/Mailnag/plugins/000077500000000000000000000000001246564717200161705ustar00rootroot00000000000000mailnag-1.1.0/Mailnag/plugins/dbusplugin.py000066400000000000000000000115111246564717200207150ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # dbusplugin.py # # Copyright 2013 - 2015 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 dbus import dbus.service from Mailnag.common.dist_cfg import DBUS_BUS_NAME, DBUS_OBJ_PATH from Mailnag.common.plugins import Plugin, HookTypes from Mailnag.common.exceptions import InvalidOperationException from Mailnag.common.i18n import _ plugin_defaults = {} class DBusPlugin(Plugin): def __init__(self): self._dbusservice = None self._mails_added_hook = None self._mails_removed_hook = None def enable(self): controller = self.get_mailnag_controller() hooks = controller.get_hooks() self._dbusservice = DBusService(controller) def mails_added_hook(new_mails, all_mails): conv_new_mails = self._convert_mails(new_mails) conv_all_mails = self._convert_mails(all_mails) self._dbusservice.set_mails(conv_all_mails) self._dbusservice.MailsAdded(conv_new_mails, conv_all_mails) def mails_removed_hook(remaining_mails): conv_remaining_mails = self._convert_mails(remaining_mails) self._dbusservice.set_mails(conv_remaining_mails) self._dbusservice.MailsRemoved(conv_remaining_mails) self._mails_added_hook = mails_added_hook self._mails_removed_hook = mails_removed_hook hooks.register_hook_func(HookTypes.MAILS_ADDED, self._mails_added_hook) hooks.register_hook_func(HookTypes.MAILS_REMOVED, self._mails_removed_hook) def disable(self): self._dbusservice = None controller = self.get_mailnag_controller() hooks = controller.get_hooks() if self._mails_added_hook != None: hooks.unregister_hook_func(HookTypes.MAILS_ADDED, self._mails_added_hook) self._mails_added_hook = None if self._mails_removed_hook != None: hooks.unregister_hook_func(HookTypes.MAILS_REMOVED, self._mails_removed_hook) self._mails_removed_hook = None def get_manifest(self): return (_("DBus Service"), _("Exposes Mailnag's functionality via a DBus service."), "1.1", "Patrick Ulbrich ", True) def get_default_config(self): return plugin_defaults def has_config_ui(self): return False def get_config_ui(self): return None def load_ui_from_config(self, config_ui): pass def save_ui_to_config(self, config_ui): pass def _convert_mails(self, mails): converted_mails = [] for m in mails: d = {} name, addr = m.sender d['datetime'] = m.datetime # int32 (i) d['subject'] = m.subject # string (s) d['sender_name'] = name # string (s) d['sender_addr'] = addr # string (s) d['id'] = m.id # string (s) converted_mails.append(d) return converted_mails # DBUS server that exports Mailnag signals and methods class DBusService(dbus.service.Object): def __init__(self, mailnag_controller): self._mails = [] self._mailnag_controller = mailnag_controller bus_name = dbus.service.BusName(DBUS_BUS_NAME, bus = dbus.SessionBus()) dbus.service.Object.__init__(self, bus_name, DBUS_OBJ_PATH) def set_mails(self, mails): self._mails = mails @dbus.service.signal(dbus_interface = DBUS_BUS_NAME, signature = 'aa{sv}aa{sv}') def MailsAdded(self, new_mails, all_mails): pass @dbus.service.signal(dbus_interface = DBUS_BUS_NAME, signature = 'aa{sv}') def MailsRemoved(self, remaining_mails): pass @dbus.service.method(dbus_interface = DBUS_BUS_NAME, out_signature = 'aa{sv}') def GetMails(self): return self._mails @dbus.service.method(dbus_interface = DBUS_BUS_NAME, out_signature = 'u') def GetMailCount(self): return len(self._mails) @dbus.service.method(dbus_interface = DBUS_BUS_NAME) def Shutdown(self): try: self._mailnag_controller.shutdown() except InvalidOperationException: pass @dbus.service.method(dbus_interface = DBUS_BUS_NAME) def CheckForMails(self): try: self._mailnag_controller.check_for_mails() except InvalidOperationException: pass @dbus.service.method(dbus_interface = DBUS_BUS_NAME, in_signature = 's') def MarkMailAsRead(self, mail_id): self._mails = filter(lambda m: m['id'] != mail_id, self._mails) try: self._mailnag_controller.mark_mail_as_read(mail_id) except InvalidOperationException: pass mailnag-1.1.0/Mailnag/plugins/libnotifyplugin.py000066400000000000000000000252001246564717200217570ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # libnotifyplugin.py # # Copyright 2013 - 2015 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 dbus import threading from gi.repository import Notify, Gio, Gtk from Mailnag.common.plugins import Plugin, HookTypes from Mailnag.common.i18n import _ from Mailnag.common.subproc import start_subprocess from Mailnag.common.exceptions import InvalidOperationException from Mailnag.daemon.mails import sort_mails MAX_VISIBLE_MAILS_LIMIT = 20.0 NOTIFICATION_MODE_COUNT = '0' NOTIFICATION_MODE_SUMMARY = '1' NOTIFICATION_MODE_SINGLE = '2' plugin_defaults = { 'notification_mode' : NOTIFICATION_MODE_SUMMARY, 'max_visible_mails' : '10' } class LibNotifyPlugin(Plugin): def __init__(self): # dict that tracks all notifications that need to be closed self._notifications = {} self._initialized = False self._lock = threading.Lock() self._notification_server_wait_event = threading.Event() self._notification_server_ready = False self._is_gnome = False self._mails_added_hook = None self._mails_removed_hook = None def enable(self): self._max_mails = int(self.get_config()['max_visible_mails']) self._notification_server_wait_event.clear() self._notification_server_ready = False self._notifications = {} # initialize Notification if not self._initialized: Notify.init("Mailnag") self._is_gnome = os.environ.has_key('GDMSESSION') and \ (os.environ['GDMSESSION'] == 'gnome') self._initialized = True def mails_added_hook(new_mails, all_mails): self._notify_async(new_mails, all_mails) def mails_removed_hook(remaining_mails): if remaining_mails == 0: # no mails (e.g. email client has been launched) -> close notifications self._close_notifications() self._mails_added_hook = mails_added_hook self._mails_removed_hook = mails_removed_hook controller = self.get_mailnag_controller() hooks = controller.get_hooks() hooks.register_hook_func(HookTypes.MAILS_ADDED, self._mails_added_hook) hooks.register_hook_func(HookTypes.MAILS_REMOVED, self._mails_removed_hook) def disable(self): controller = self.get_mailnag_controller() hooks = controller.get_hooks() if self._mails_added_hook != None: hooks.unregister_hook_func(HookTypes.MAILS_ADDED, self._mails_added_hook) self._mails_added_hook = None if self._mails_removed_hook != None: hooks.unregister_hook_func(HookTypes.MAILS_REMOVED, self._mails_removed_hook) self._mails_removed_hook = None # Abort possible notification server wait self._notification_server_wait_event.set() # Close all open notifications # (must be called after _notification_server_wait_event.set() # to prevent a possible deadlock) self._close_notifications() def get_manifest(self): return (_("LibNotify Notifications"), _("Shows a popup when new mails arrive."), "1.1", "Patrick Ulbrich ", False) def get_default_config(self): return plugin_defaults def has_config_ui(self): return True def get_config_ui(self): box = Gtk.Box() box.set_spacing(12) box.set_orientation(Gtk.Orientation.VERTICAL) label = Gtk.Label() label.set_markup('%s' % _('Notification mode:')) label.set_alignment(0.0, 0.0) box.pack_start(label, False, False, 0) inner_box = Gtk.Box() inner_box.set_spacing(6) inner_box.set_orientation(Gtk.Orientation.VERTICAL) cb_count = Gtk.RadioButton(label = _('Count of new mails')) inner_box.pack_start(cb_count, False, False, 0) cb_summary = Gtk.RadioButton(label = _('Summary of new mails'), group = cb_count) inner_box.pack_start(cb_summary, False, False, 0) cb_single = Gtk.RadioButton(label = _('One notification per new mail'), group = cb_count) inner_box.pack_start(cb_single, False, False, 0) alignment = Gtk.Alignment() alignment.set_padding(0, 6, 18, 0) alignment.add(inner_box) box.pack_start(alignment, False, False, 0) label = Gtk.Label() label.set_markup('%s' % _('Maximum number of visible mails:')) label.set_alignment(0.0, 0.0) box.pack_start(label, False, False, 0) spinner = Gtk.SpinButton.new_with_range(1.0, MAX_VISIBLE_MAILS_LIMIT, 1.0) alignment = Gtk.Alignment() alignment.set_padding(0, 0, 18, 0) alignment.add(spinner) box.pack_start(alignment, False, False, 0) return box def load_ui_from_config(self, config_ui): config = self.get_config() inner_box = config_ui.get_children()[1].get_child() cb = inner_box.get_children()[int(config['notification_mode'])] cb.set_active(True) max_mails = float(config['max_visible_mails']) spinner = config_ui.get_children()[3].get_child() spinner.set_value(max_mails) def save_ui_to_config(self, config_ui): config = self.get_config() inner_box = config_ui.get_children()[1].get_child() idx = 0 for cb in inner_box.get_children(): if cb.get_active(): config['notification_mode'] = str(idx) break idx += 1 spinner = config_ui.get_children()[3].get_child() max_mails = spinner.get_value() config['max_visible_mails'] = str(int(max_mails)) def _notify_async(self, new_mails, all_mails): def thread(): with self._lock: # The desktop session may have started Mailnag # before the libnotify dbus daemon. if not self._notification_server_ready: if not self._wait_for_notification_server(): return self._notification_server_ready = True config = self.get_config() if config['notification_mode'] == NOTIFICATION_MODE_COUNT: self._notify_count(len(all_mails)) elif config['notification_mode'] == NOTIFICATION_MODE_SUMMARY: self._notify_summary(new_mails, all_mails) else: self._notify_single(new_mails) t = threading.Thread(target = thread) t.start() def _notify_summary(self, new_mails, all_mails): summary = "" body = "" # The mail list (all_mails) is sorted by date (mails with most recent # date on top). New mails with no date or older mails that come in # delayed won't be listed on top. So if a mail with no or an older date # arrives, it gives the impression that the top most mail (i.e. the mail # with the most recent date) is re-notified. # To fix that, simply put new mails on top explicitly. mails = new_mails + [m for m in all_mails if m not in new_mails] if len(self._notifications) == 0: self._notifications['0'] = self._get_notification(" ", None, None) # empty string will emit a gtk warning ubound = len(mails) if len(mails) <= self._max_mails else self._max_mails for i in range(ubound): if self._is_gnome: body += "%s:\n%s\n\n" % (self._get_sender(mails[i]), mails[i].subject) else: body += "%s - %s\n" % (ellipsize(self._get_sender(mails[i]), 20), ellipsize(mails[i].subject, 20)) if len(mails) > self._max_mails: if self._is_gnome: body += "%s" % _("(and {0} more)").format(str(len(mails) - self._max_mails)) else: body += _("(and {0} more)").format(str(len(mails) - self._max_mails)) if len(mails) > 1: # multiple new emails summary = _("{0} new mails").format(str(len(mails))) else: summary = _("New mail") self._notifications['0'].update(summary, body, "mail-unread") self._notifications['0'].show() def _notify_single(self, mails): # In single notification mode new mails are # added to the *bottom* of the notification list. mails = sort_mails(mails, sort_desc = False) for mail in mails: n = self._get_notification(self._get_sender(mail), mail.subject, "mail-unread") notification_id = str(id(n)) if self._is_gnome: n.add_action("mark-as-read", _("Mark as read"), self._notification_action_handler, (mail, notification_id)) n.show() self._notifications[notification_id] = n def _notify_count(self, count): if len(self._notifications) == 0: self._notifications['0'] = self._get_notification(" ", None, None) # empty string will emit a gtk warning if count > 1: # multiple new emails summary = _("{0} new mails").format(str(count)) else: summary = _("New mail") self._notifications['0'].update(summary, None, "mail-unread") self._notifications['0'].show() def _close_notifications(self): with self._lock: for n in self._notifications.itervalues(): n.close() self._notifications = {} def _get_notification(self, summary, body, icon): n = Notify.Notification.new(summary, body, icon) n.set_category("email") if self._is_gnome: n.add_action("default", "default", self._notification_action_handler, None) return n def _wait_for_notification_server(self): bus = dbus.SessionBus() while not bus.name_has_owner('org.freedesktop.Notifications'): self._notification_server_wait_event.wait(5) if self._notification_server_wait_event.is_set(): return False return True def _notification_action_handler(self, n, action, user_data): with self._lock: if action == "default": mailclient = get_default_mail_reader() if mailclient != None: start_subprocess(mailclient) # clicking the notification bubble has closed all notifications # so clear the reference array as well. self._notifications = {} elif action == "mark-as-read": controller = self.get_mailnag_controller() try: controller.mark_mail_as_read(user_data[0].id) except InvalidOperationException: pass # clicking the action has closed the notification # so remove its reference. del self._notifications[user_data[1]] def _get_sender(self, mail): name, addr = mail.sender if len(name) > 0: return name else: return addr 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 def ellipsize(str, max_len): if max_len < 3: max_len = 3 if len(str) <= max_len: return str else: return str[0:max_len - 3] + '...' mailnag-1.1.0/Mailnag/plugins/soundplugin.py000066400000000000000000000056311246564717200211160ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # soundplugin.py # # Copyright 2013 - 2015 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 threading from gi.repository import Gst from Mailnag.common.plugins import Plugin, HookTypes from Mailnag.common.utils import get_data_file from Mailnag.common.i18n import _ plugin_defaults = { 'soundfile' : 'mailnag.ogg' } class SoundPlugin(Plugin): def __init__(self): self._mails_added_hook = None def enable(self): def mails_added_hook(new_mails, all_mails): config = self.get_config() gstplay(get_data_file(config['soundfile'])) self._mails_added_hook = mails_added_hook controller = self.get_mailnag_controller() hooks = controller.get_hooks() hooks.register_hook_func(HookTypes.MAILS_ADDED, self._mails_added_hook) def disable(self): controller = self.get_mailnag_controller() hooks = controller.get_hooks() if self._mails_added_hook != None: hooks.unregister_hook_func(HookTypes.MAILS_ADDED, self._mails_added_hook) self._mails_added_hook = None def get_manifest(self): return (_("Sound Notifications"), _("Plays a sound when new mails arrive."), "1.1", "Patrick Ulbrich ", False) def get_default_config(self): return plugin_defaults def has_config_ui(self): return False def get_config_ui(self): # TODO : Add ui to specify the path # of a custom sound file. return None def load_ui_from_config(self, config_ui): pass def save_ui_to_config(self, config_ui): pass class _GstPlayThread(threading.Thread): def __init__(self, ply): self.ply = ply threading.Thread.__init__(self) def run(self): def on_eos(bus, msg): # loggin.debug('EOS') 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: ply = Gst.ElementFactory.make("playbin", "player") ply.set_property("uri", "file://" + os.path.abspath(filename)) pt = _GstPlayThread(ply) pt.start() except: pass mailnag-1.1.0/Mailnag/plugins/spamfilterplugin.py000066400000000000000000000073651246564717200221420ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # smamfilterplugin.py # # Copyright 2013 - 2015 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 Gtk from Mailnag.common.plugins import Plugin, HookTypes from Mailnag.common.i18n import _ plugin_defaults = { 'filter_text' : 'newsletter, viagra' } class SpamfilterPlugin(Plugin): def __init__(self): self._filter_mails_hook = None self._filter_list = None def enable(self): config = self.get_config() self._filter_list = config['filter_text'].replace('\n', '').split(',') def filter_mails_hook(mails): lst = [] for m in mails: if not self._is_filtered(m): lst.append(m) return lst self._filter_mails_hook = filter_mails_hook controller = self.get_mailnag_controller() hooks = controller.get_hooks() hooks.register_hook_func(HookTypes.FILTER_MAILS, self._filter_mails_hook) def disable(self): controller = self.get_mailnag_controller() hooks = controller.get_hooks() if self._filter_mails_hook != None: hooks.unregister_hook_func(HookTypes.FILTER_MAILS, self._filter_mails_hook) self._filter_mails_hook = None self._filter_list = None def get_manifest(self): return (_("Spam Filter"), _("Filters out unwanted mails."), "1.1", "Patrick Ulbrich ", False) def get_default_config(self): return plugin_defaults def has_config_ui(self): return True def get_config_ui(self): box = Gtk.Box() box.set_spacing(12) box.set_orientation(Gtk.Orientation.VERTICAL) #box.set_size_request(100, -1) desc = _('Mailnag will ignore mails containing at least one of \nthe following words in subject or sender.') label = Gtk.Label(desc) label.set_line_wrap(True) #label.set_size_request(100, -1); box.pack_start(label, False, False, 0) scrollwin = Gtk.ScrolledWindow() scrollwin.set_shadow_type(Gtk.ShadowType.IN) scrollwin.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) txtbuffer = Gtk.TextBuffer() txtview = Gtk.TextView() txtview.set_buffer(txtbuffer) txtview.set_wrap_mode(Gtk.WrapMode.WORD) scrollwin.add(txtview) box.pack_start(scrollwin, True, True, 0) return box def load_ui_from_config(self, config_ui): config = self.get_config() txtview = config_ui.get_children()[1].get_child() txtview.get_buffer().set_text(config['filter_text']) def save_ui_to_config(self, config_ui): config = self.get_config() txtview = config_ui.get_children()[1].get_child() txtbuffer = txtview.get_buffer() start, end = txtbuffer.get_bounds() config['filter_text'] = txtbuffer.get_text(start, end, False) def _is_filtered(self, mail): is_filtered = False for f in self._filter_list: # remove CR and white space f = f.strip() if len (f) == 0: continue f = f.lower() sender_name, sender_addr = mail.sender if (f in sender_name.lower()) or (f in sender_addr.lower()) \ or (f in mail.subject.lower()): # sender or subject contains filter string is_filtered = True break return is_filtered mailnag-1.1.0/Mailnag/plugins/userscriptplugin.py000066400000000000000000000070311246564717200221650ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # userscriptplugin.py # # Copyright 2013 - 2015 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 from gi.repository import Gtk from Mailnag.common.plugins import Plugin, HookTypes from Mailnag.common.i18n import _ from Mailnag.common.subproc import start_subprocess plugin_defaults = { 'script_file' : '' } class UserscriptPlugin(Plugin): def __init__(self): self._mails_added_hook = None def enable(self): def mails_added_hook(new_mails, all_mails): self._run_userscript(new_mails) self._mails_added_hook = mails_added_hook controller = self.get_mailnag_controller() hooks = controller.get_hooks() hooks.register_hook_func(HookTypes.MAILS_ADDED, self._mails_added_hook) def disable(self): controller = self.get_mailnag_controller() hooks = controller.get_hooks() if self._mails_added_hook != None: hooks.unregister_hook_func(HookTypes.MAILS_ADDED, self._mails_added_hook) self._mails_added_hook = None def get_manifest(self): return (_("User Script"), _("Runs an user defined script on mail arrival."), "1.1", "Patrick Ulbrich ", False) def get_default_config(self): return plugin_defaults def has_config_ui(self): return True def get_config_ui(self): box = Gtk.Box() box.set_spacing(12) box.set_orientation(Gtk.Orientation.VERTICAL) #box.set_size_request(100, -1) markup_str = "<%s> <%s>" % (_('sender'), _('subject')) desc = _( "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 %s pairs." % markup_str) label = Gtk.Label() label.set_line_wrap(True) label.set_markup(desc) #label.set_size_request(100, -1); box.pack_start(label, False, False, 0) filechooser = Gtk.FileChooserButton() box.pack_start(filechooser, True, True, 0) return box def load_ui_from_config(self, config_ui): config = self.get_config() script_file = config['script_file'] if len(script_file) > 0: filechooser = config_ui.get_children()[1] filechooser.set_filename(script_file) def save_ui_to_config(self, config_ui): config = self.get_config() filechooser = config_ui.get_children()[1] script_file = filechooser.get_filename() if script_file == None: script_file = '' config['script_file'] = script_file def _run_userscript(self, new_mails): config = self.get_config() script_file = config['script_file'].strip() if (len(script_file) > 0) and os.path.exists(script_file): script_args = [ script_file, str(len(new_mails)) ] for m in new_mails: sender_name, sender_addr = m.sender if len(sender_addr) == 0: sender_addr = 'UNKNOWN_SENDER' script_args.append(sender_addr) script_args.append(m.subject) start_subprocess(script_args) mailnag-1.1.0/NEWS000066400000000000000000000111771246564717200136450ustar00rootroot00000000000000Version 1.1.0 (2015-02-08): =========================== * GNOME Online Accounts integration (via a plugin -> https://github.com/pulb/mailnag-goa-plugin) * Added support for platform-specific credential (login password) backends (Configurable in the config file. Supported options are currently "AUTO", "GNOME" and "NONE".) * The GNOME-Keyring dependency is optional now * Improved connectivity tests (Configurable in the config file. Supported options are "AUTO", "NETWORKMANAGER" and "PING".) * Shorter notification strings * Added appdata file for GNOME Software app * Removed bash wrapper scripts * Misc other fixes and improvements * Updated translations IMPORTANT NOTES FOR PACKAGERS: Please note that GNOME-Keyring and Networkmanager are now optional dependencies and the httplib2 dependency is not required anymore. Version 1.0.0 (2014-06-28): =========================== * Mailnag is desktop-independent now (was GNOME3-only) * Added plugin system to allow easy extensibility * Cut down the core daemon functionality to mail checking only and moved everything else to plugins * Released GNOME 3/Ubuntu Unity extension/plugin for a tighter desktop integration * Added account assistants for popular email providers like Gmail * Added DBUS service (for remote control/integration in other apps) * Redesigned and simplified config UI * Added propper logging (log messages are sent to stdout and the system log now) * Heavy code cleanup & refactoring * Reduced disk write access, other performance improvements * Fixes for various major and minor bugs * New icon by Reda Lazri IMPORTANT NOTES: The fileformat of the config file has changed. It's highly recommended to rename/backup your current config file (~/.config/mailnag/mailnag.cfg). Version 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 mailnag-1.1.0/README.md000066400000000000000000000065551246564717200144310ustar00rootroot00000000000000# Mailnag An extensible mail notification daemon. Mailnag is a daemon program that checks POP3 and IMAP servers for new mail. On mail arrival it performs various actions provided by plugins. Mailnag comes with a set of desktop-independent default plugins for visual/sound notifications, script execution etc. and can be extended with additional plugins easily. __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. ### Arch Linux Mailnag is available in the [AUR](https://aur.archlinux.org/packages/mailnag/) repository. Please either run `yaourt -S mailnag` or `packer -S mailnag` (as root) 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 (>= 0.7.6) * gir-gtk-3.0 * gir-gdkpixbuf-2.0 * gir-glib-2.0 * gir-gst-plugins-base-1.0 * python-dbus * pyxdg * gettext * gir-gnomekeyring-1.0 (optional) * networkmanager (optional) ## 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). ### Desktop integration By default, Mailnag emits libnotify notifications, which work fine on most desktop environments but are visible for a few seconds only. If you like to have a tighter desktop integration (e.g. a permanently visible indicator in your top panel) you have to install an appropriate extension/plugin for your desktop shell. Currently the following desktop shells are supported: * GNOME-Shell ([GNOME-Shell extension](https://github.com/pulb/mailnag-gnome-shell)) * Ubuntu Unity ([MessagingMenu plugin](https://github.com/pulb/mailnag-unity-plugin)) Furthermore, I highly recommend GNOME users to install the [GOA plugin](https://github.com/pulb/mailnag-goa-plugin), which makes Mailnag aware of email accounts specified in GNOME Online Accounts. ## Screenshots ![Screenshot](https://raw.githubusercontent.com/pulb/mailnag-design/master/Flyer/Mailnag_flyer2.png) mailnag-1.1.0/data/000077500000000000000000000000001246564717200140505ustar00rootroot00000000000000mailnag-1.1.0/data/account_dialog.ui000066400000000000000000000314501246564717200173650ustar00rootroot00000000000000 False 5 Mail Account False True dialog False vertical 18 False end gtk-cancel True True True True True True 0 gtk-ok True False True True True True True 1 False True end 0 True False 6 6 6 6 6 True True True number 1 5 True True True 1 4 True True True False 1 3 True True True 1 2 True True True 1 1 True True True 1 6 Enable Push-IMAP True True False 0 True 0 7 2 True False 1 0 Enable SSL encryption True True False 0 True 0 8 2 True False 0 Accountname: 0 1 True False 0 Account type: 0 0 True False 0 User: 0 2 True False 0 Password: 0 3 True False 0 Server: 0 4 True False 0 Port: 0 5 True False 0 Folders: 0 6 True True 1 button_cancel button_save mailnag-1.1.0/data/appdata/000077500000000000000000000000001246564717200154625ustar00rootroot00000000000000mailnag-1.1.0/data/appdata/mailnag.appdata.xml000066400000000000000000000023541246564717200212310ustar00rootroot00000000000000 mailnag-config.desktop CC0-1.0 GPL-2.0+ Mailnag An extensible mail notification daemon Mailnag is a daemon program that checks POP3 and IMAP servers for new mail. On mail arrival it performs various actions provided by plugins. Mailnag comes with a set of desktop-independent default plugins for visual/sound notifications, script execution etc. and can be extended with additional plugins easily. https://raw.githubusercontent.com/pulb/mailnag-design/master/Screenshots/mailnag-config.png https://raw.githubusercontent.com/pulb/mailnag-design/master/Screenshots/mailnag-gnome-shell2.png https://raw.githubusercontent.com/pulb/mailnag-design/master/Screenshots/mailnag-gnome-shell.png https://www.github.com/pulb/mailnag/ zulu99@gmx.net mailnag-1.1.0/data/config_window.css000066400000000000000000000002251246564717200174150ustar00rootroot00000000000000GtkEventBox { background-image: linear-gradient(to bottom, @theme_bg_color, shade(@theme_bg_color, 0.94) 12%); } mailnag-1.1.0/data/config_window.ui000066400000000000000000000423751246564717200172560ustar00rootroot00000000000000 400 380 False Mailnag Configuration False True False vertical True False 18 18 12 12 True General True True True False True 0 Accounts True True True False True 1 Plugins True True True False True 2 False True 0 True True False False True False True False vertical 12 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> - An extensible mail notification daemon. Copyright (c) 2011 - 2015 Patrick Ulbrich and contributors. True False False 1 True True center False False 2 True False vertical True True in True True liststore_accounts False True False 1 True True 0 True False icons 1 True False Add Account list-add-symbolic False True True False Remove Account list-remove-symbolic False True True False Edit Account text-editor-symbolic False True False True 1 1 True False vertical True True in True True liststore_plugins False True False 1 True True 0 True False icons 1 True False Edit Plugin text-editor-symbolic False True False True 1 2 True True 1 mailnag-1.1.0/data/icons/000077500000000000000000000000001246564717200151635ustar00rootroot00000000000000mailnag-1.1.0/data/icons/hicolor/000077500000000000000000000000001246564717200166225ustar00rootroot00000000000000mailnag-1.1.0/data/icons/hicolor/128x128/000077500000000000000000000000001246564717200175575ustar00rootroot00000000000000mailnag-1.1.0/data/icons/hicolor/128x128/apps/000077500000000000000000000000001246564717200205225ustar00rootroot00000000000000mailnag-1.1.0/data/icons/hicolor/128x128/apps/mailnag.png000066400000000000000000000220651246564717200226450ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATxy$}?飯ݙSmR+Xe1aBDZ$V#FvBcĐs  C@"HMI)ky}3Wzz힙ݥ=j{W}js v/wrL1!.DŽrL1!.DŽrL1!.DŽrL1!.DŽrL1!.DŽn`?A' AhlriG>{fw Nǩ}?Gnl@ 6!Bm[0˿#Ϗ% Ixax$d->K9^[0?,G{eߧ90 ^AXp(6Q'g(4ෑ:ܫ/MX.Asuǿե'p6BA~k\5A7\x70.᳟{ >i.YR ԛ'XıO>~qLpBWj{>Ky ,fl lnW43\ ؿs=M*Xk7č2V@c-BԧG;y_"%)wg;}5@ ]f6!pߑ |=sO$vƒP 9h=MB @@{ :"t r 0(<aSH1@S':8+W؄Q7W\-i:J;b+DMӡMAv \ٿ3O2;mhpq6řpHo^Hg>_j嫩R*#jJΉ>I>^Ա8曊#{Bf {:Ǚ) by-3Zᰵa AlzΩ_&o} #T߂Wdl>@O]e+́~uXG>?͏s ʦ!3/v9}=c:=chT6을VK=3@BSB痼_ qV#D5ր$7">}lD Z"Կ{&#Tї~txѭ^f1me._zJbJdi%xPy6ۮur:>ֹ ۛuZR *Y n >,,P&0Y36ۨQ5FYl ϝpv)CU`- >szQ{d70+r+ f<:s|O8q'xLT=˱6%7dZml2> At?z5O,rBkZ{! k{r:nsm?*%'̵ӗlźUV_oqfqst&{bRqMsWOw9*^ jՄ/1c'η2*Gqng8ͳ]skAPpt&d.YI3>'-{#{>gfH~s_#EN G>d>K|v9ɡo@""2+ecXI[bJrVXBs4_?Z,Ϟpn&CUB}{4w<9f*TNӷm GiGϐ4pITm@!Z 9Z7ѵ@(LۤbO6_Dr|k4 vg\\,rvbeH>|4jon塽Xy_ חB->Yܑf` DIAjL_jɍ! kR]#o~xcT֪5<'2(E/sr0Nĕ qqOCS51N O恹7!((ʭ[0ÇT5 iTu8C` 5 -ʼ8q, saLDaDZ:׆IPi@X&X[zͻGwZ'XyBW, N,$\\K6'(Xh|T֫~g'#IrXQE ד{H$t9'W~D ^@g2%37҇c͌֊( Aa )$ur`1G3v/_sn9cǪ쭮=N x gGF 02^}헙$"05ZIWZj\87 } r2|ڂ' L~㱏m>ޱD)R 4ZH!PJFkEJpT\9nb^]{"9Pِ<)~u;1R^RǎWߜ{?>e7~hl{3[ !0^,x]Ӑ>mн~%f . -0yr+kop,t刟|?C~Z,A&}8!Guϙ'4 KKG<{Gw Zy|w6s|R ˾H>z,Pd3L7:\T4Q"m7Ņ#Z.Seh,%װ{ ԩԥgov;[3A 3\+RB\ Qŵm;T3}|OB9*[}WNvYlwOGDگr?/,d|l~0N55Zb:EVCtSÂ!p2rEvlE'&sүj@ @ HM֚@kڠ={YV{1#9~Ŕ'WGkN{NHI9 8ܹfE{mt|Ư*:rВ,ND9 M̘R/B0 '(c\[?޿uW֊wL QxBf@h() mЁf!-SK\ I\Ȩ:^G+<8Q _:+ȭFq$bwm|baáBd@BA5Z)*E$+\Yq{6F |.VƷ竦XqnuOz(UŜH]E*UuW#N :*a q !v'qyHp"*GPjBm#<,DNTr>?(#z4+NRt3Ntx`6bBVLE']㥋 8ܴ4,"T`K(G1X85?>獫i嵀;r\U1Eu(3(A=t`o1Xbĉ'858!θO㵅"A+zZY|Dq~$ztP 5`UL4U% CJ 42#d'-p"1B 5 R;c6kZԐoࣅ G{z+JwDmZ]cGT*ĒO?\ z!jy"qWCx`XkQJ[ڙ./]H(rq?Ԫs @ihuB>OMA@x ʤ-!EkqqG^1 (L6PE'^ⲫs-c%e쥋 r>y_́#!"Lk~NX3j}'_)Ҋ+,ˌ,C6v+Z0ߐԳ+LeĵJQTQJ1%k\p[JUok{CHyZ+PM PIj4W{X6!7&y{#>x(PXW.^[3ȑ-/zg %q@]3eT:%j([,4Lr;0R6@qpG_Sވ_DDZ4Q#+p6\?aAyj§*)$ 4OmJ~C'S 8/&ծm͠e ^`%Q]VB/( ( {!\.CwA>!@݊ iS뷩,fNbzl>.&9B[/F:z{p[ `naՁ^Xk}Y!P$A@UeV)Kk797Q()];X}=N5U.ȡ #-"Tb/_du¾GQVzP6^ n`l:^OTUփβc`2Ŋ/,>A1A{X8EhXjPupq%Uyhh^ [djNkt0tt^b+\ *\\2T$}rBSoxm'u*;n( D+{#}f{jTGvH! pA_Yh9($;J+6aRD8vXkh0 hHӔ<7BUJ)1/7OyTe95W|XsUVp/D I<8RMاrj 9;;K$Qt: Rn ÈJeu D)K$(|BG)sGJIg:]* T%*i.sKƺ"\Dh3-͠Zz(Ŭl4Z; }9E#@FJ2~kV̈́L9a&I1i}s3!MWc<'M3sP3&o_Oirl޵![n 25(&~yZgjoO`iM\J{*E<$ nƘ=49KףYNs$I2AAmnvi,J)Agp YG) +Wp9}=]mt>"#>36 GFl3 g]QhJ# J^M@o.;I!@$T!~Z.YWldY^ kO68^LŒq0 &708Д;W +1{Ꜹ縢%Tٳ#g}f|)ѡ6SEi={Bzs}19GE(h}8N`p@4S'Vm~8ϲQ?sJ8" I2*;BE% 2cZ?aD^ζ,'>1jBI _̱iE#ުR -P"F͗i)$ly`7L(пݪ$)q ?˨*Z5S [tFQh8EEܣZq/vh'>[F %ՀGhe^ќBzhJ]Ϲ1(aiiRl믳UTY慹W5"},FJ#_97<\ efBPK.iŌS7Z;sߞl"J_5ʁ ,o!0&G)4VknI!@%΍}?Ð,a={C1A{Wޗ( 6֓L))'D9S+WfW.R~.s{с&PYyqV`If yYQ& {g r0:Q"Q4EEH3B69sy oV̚c 8+Χ}3SUI4ט+xYAXmo ɑJQ ܭN+Qenp#œ- %7nK%Z}e kQH'+ϰb(C{ST[WPbT`^ Ƴ߉:5aZ~%q 孷:Vn)6k-B H)j `'CU13lT͚%aJ2k@U8/. Vlb[$\I / r k77^! P".~ ڃqsnPXִlP~u+v$`B/ݐB^AImCmo&a!DTjq3v H`P*nZ1jJMe 7xdm.~mZY 4 ZX`'WmB٥SEU/|V¤T*EP : G ގBh?{w6ۍrq(h6kZGT n;ضPJ+Tk>CZ- ^)%Q$ z!hͰJ$pNl(wfoUHh6iZZ&/Ǵ (W)e>A4==Mn|_['|XVU>ßanZӂshl|gMpð<ϙV#XaMO05,5crZoVD,49Fn{HUUXc333hT*Z(*p;2w/pO)ƎdshI h'm͆HAPd뼣I(*j_VR K;np"M* %bW}IDATQo¢t + BJzv5xCr)MmP(gRPTpA*׫w}SZNVbMJU4 y6 sX/:J_kT1h 0ndYT8.sbˇV պ2QΡF1W^Ï9v,cxAeίz7Zp+(h6}?8 i6 OaJ۾1DBJAZ]"L2/ĕuɛʯ(L#pUP"[2l0P2ۖ/5K/SAY;nl(ei;˲5% >~4MIduɟii /\YO~ H+MS.\8u6o_ -`gcA:g,&鵋.槾S[[-G!@IPnChh, Z)|h3wohsNEntTYmo$|= (meU 7JޮvJ;uvscES?L&838 Gn[MNg촠Fΰl6#Mq+ ~X+I$٭-ޭZ<6DX;; vR #nH5$ c\aNq,|J0"` `}coح^fрψ0)jw|;ߍ*{`L9pt2.Dw:6Їe4.,mYb;w &;aƄrL1!.DŽrL1!.DŽrL1!.DŽrL1!.DŽrOIENDB`mailnag-1.1.0/data/icons/hicolor/16x16/000077500000000000000000000000001246564717200174075ustar00rootroot00000000000000mailnag-1.1.0/data/icons/hicolor/16x16/apps/000077500000000000000000000000001246564717200203525ustar00rootroot00000000000000mailnag-1.1.0/data/icons/hicolor/16x16/apps/mailnag.png000066400000000000000000000011331246564717200224660ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDAT8œ1OSasrM6Xa0@Qč:999EI)9PM ޶>HH<>yXk8jNZz_lrzM *OJb!j`Aar>4=4|e jKRX~F5qZ^a ߖïXSDܻYɝYJ~\B6Шz.!n\G 3g혭k+*Q"QJ̨1 &Wi͘FJΏMQ WFtDj'HX [ hsH}AkxgtkQn E."4c(ލ8ZC0[pZN~jyDsj10a-?|ZƘq֚0B)y<\ ZcӖ$pIENDB`mailnag-1.1.0/data/icons/hicolor/22x22/000077500000000000000000000000001246564717200174015ustar00rootroot00000000000000mailnag-1.1.0/data/icons/hicolor/22x22/apps/000077500000000000000000000000001246564717200203445ustar00rootroot00000000000000mailnag-1.1.0/data/icons/hicolor/22x22/apps/mailnag.png000066400000000000000000000017201246564717200224620ustar00rootroot00000000000000PNG  IHDRĴl;sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<MIDAT8͕k\E3gn65M$҄+EDZ/ ?_^yDJ7|شMRٳ'眙3dD }^f/;ό $ww.պkiVn\nyh_h/ʋ/ oa&dI$n|\:w,鏽BT@61)uUaecs3u/[oÅ6 \d_!Njd).ڰ%ֆpv,GsP6`x=,05~9889Xfw-^-_~FgJ\>@'loo@VfWcQ٨j- ǿf0L\vE0RdaG Xn}.Z \\Gv'?3gXzzK%E( V5X`u`ή;'#!,Nyfr!t i k-% s]&C?B[m"dn+|#&uH᝺@ϻhIRg)%b zevۚV@sP%%S8>):?Q jY ]Rc,X*GS]a`?kET*8"'\+mTqՁ{GpK'e$#i*VHRC^M0==Ǿm=)M=oeaf^>X63C>sǶB-QD S1XX/fX@U!-=Lݘv}?-=:5S>9|>~;_K|y .VW3eLߊscئ k-annNe :`H78_p5be|^x̉6Ω'e1ZQz/cae)"0sy~Hַ5aARtALjn$4A%O{h Z*\h\i&tScS1DZ;Fl7~7e1$:\HꃽFm|>϶LL9rPS0[jC?^]ŮY$&\1tǬRWP jucR3Ai!/i+X AHq28' ֽccc-G8ِtgSV. +l\~ W%zc)4[-aÆ $JU]HΰqbX#2Z\۶niIҔvM'͑$ C@W+14X,-**àVEm)˲n˘aJ%R^'A@E vh~?E0 ]ַm#g~7~Ab[]Ȯ3@yk@i @Ym4,6K|s3wFfkW_'p!Yi<'w^aK@D'5}x|`ogͿq:.Lv]/(|Z[&xM y5A"|Xuٔ~"e#l5wymX: 9 @D"~_226+f&r#=287cks09Zbקiyoas8Ϡ%1P+ޟX1ocg>W$!C\DS0dS 'cchږLǜ٥m<1]O1~7AdHm&+i?9N5QgÃ~$˿_7jݙݫlۘ .G{v|?#I?#RlTs.d:cAE?:=u(L[12Jv3sZ4s\)ĮvF!3guvpL͓pyN`rۛPѸq!) wjvx:zt3='&xr37/{g9:]{ʂa:̭J/r =mp3rY@\0GhL)lN_^jF/<]DgvylO6Ԏk_uz3M&Qy >J (!!}d{TG"+ 䈊nT*शص""_g^"%y(EG0IJ:e|Ee_#H{͐$P8@)3rŹKOѿRGR )j +,;K UM7";š= =cWBxpWP.=&66gE/rbg]*sIHhDB4뿋e`?'M %dv6QzBXrHFx Xkuf' `\x]o|j o=`HA{7? mfBo=d:Ǽ01|\:o``-b->eE 1X>&C<<:ߵ2WJ+6Q3q'0nqV=-L~nPf=(>"Sそ5;k:܁QXZES]wuyzf{!8Yi;,`z䡘&58kp ?Qh_eD+"9wWlw|wr L]FXCՈY{`׋V+qyTC:9_jsiϣ4sr9'.<{os.8&a12>B2N16é Ҝۧ|jIYU HUvVV)R˶I"g}Y`]i C1k63'7fF+.rǶ'hn^ϭv'[M[e{|ŻÌI~ώcmc+ w"b]yx/jDzgJ|6\+knJ [rƮ1WRl#o\l'[wLwWGlXo]SK7)OvSK|r'w/zsnUzzn[ XL)tG6e㔽jyQ433S#dml7,algВ!h jqp\\a61N:-j˘,c` lBǞY tp<#'L .|EN{̯=>"W}u?): :HZeDY^Fmg&?#jYP3 g~ypL[0A$|SۿAGPR~Cp:F%&1va\Rdzm.-^|bHCZ,~/Ӽ'޴W }zKZ-BPO#)~ G' Nx6e?A-}Z 0Rd@ 11Q' ݹ:DZ*pk9>wű o ^g~{CCKNh73-ly+(3_hsvvc­޳pp2mu4={LC+v(b{334ta:g\wtg~4jii,4Ĵl'wsw\] *A< ]%|o# J)LHό∬Gm'h1Di6g=frp]c;e%uM||{3\]y{&^e3 fX~m4bţw|ϐ.Cdn MPpnFPҿQfo|ht(q];1l2{l>(5*w-=uL,s9Gw8ҚET߇ߔ|'z^ԝB~$ED0=5iZ>C#Fr6K=ç[])߳^3eqh-y~;S޸b :u 7Ug5q%7M:B"!ZH"E&$wkrV_lJarq>GLE\foӟo=XOk-c!/Z`2(1d,Hk6ptTBۏPžpz"B߷_S">^P{2ZkT`XMNA+$P.j)zce'76wxJݯt/koZal'=xxw9O_+]=r1nL½&$ !7Si4L3uev:3+ި 3ѪEGeT5gJ*$99ѳ] @um46d. 3Cnrs>rxE48v>o?¢f =k=yل"O>?ǼمE=ζgB+YW4p%B$d}wplst{)vRΆZbBp*;N TP,DAWN\x@eaŠzfubCR I?J,J ը "($|Fz'LaMp39"s|G ஔ}zÍƵ"5xzo\nZ%f;ԙVޢ1Rƙ[h#5e2.Qc1$|mr@ v?A*&WZ7o^\pJ]:_~]뿮tx}F #5D0V ">Rt$0Vňkwq|\sS_gZDMxr˷i|'d.pF1w|\K Lڕ#sw$\3wTg݋7X̸r_ y.R\X՛qUHu'ǒwp/ʗ['t{), GpFk IKʁ\j}P*Cc4jBzX;ƒG)sWyp֟=8fvm3~z{J|VWwmxFc./hbQODθÌ5oѣ-e .)=V P}KyUņP~a_]EI Y-n *+J ڬ\4LJ..Zw}A)MqL>CCOp59]%Ft3[NCuuN74?ߺc4*3-%39o?`:ro>Pd϶Yڤ!m¢ s_<ٴUR;4=׹J).g|lv/G > àۃdzAG:{= ڥN R88T)RU \IHlH͒gB'8}؏9bMP} IDAT TbLDP䒣CW=!"t$t'WnPݥ.΄73kLc ]yğ<118܍mi΄+vjEck;<5 Ur}hC>UY9?&w5r?{wC@W/g+J``|꬏Y07``SZ=?ZQ?{>|'Å(l@mJWhEĄ,lsKoO6c\[$r]PB H.J6(J+`VYb?f@(]ˋ=a @)◫sJR+)!\dJPL2ୁ<qҭ݁V+P-t-ۓO=0g^3Ηu8}ksf٥={_0qÓ3yXMsP5#"ed:7/xbgL-*%gMACD ^_|ǚ#S5nڃLs[~.JJF~  ~ ȼhQ\0\p a})D+~߼W%,Q|E, 6Џ(Ardx>QZ" 6\%6H8S/eLM#kph[ϵF3=.hM?-δC-UǦ2Z//1|T[B}SfE 0(.ElD,-'gZ '?5q-da]j x s]8ׂU8P/,_+/WR_$^2 3$-0 a_5E`TvnDaBFfO.5^giGEOtp[e m?ġo_²Q1_G4U0K~gǷBƥ1g٣4%L,^y `HHTq).<8qn6 @J3XEf aPfWQ]r~[Lm}rK)+9yicL ^]X eYXY>w%crD:^)H9D[C >_B3c~X}42Y^#OL2At`("呇BAEA L>SC|Cg։#Zks ց<(!s(}A&Y7B wUKЩG37o/=^V u8q$rV!+ONv?KXM\);e``%"##JFy}T%(=N{2[;JKꤑo=Q0{O.uo__"~OɈϜpy jW@5굣y硘!FÂ^%͌/At>s $Nsv@yK@amE.$쐨.Y^DGz=ua;SߢDy | dCx!eG!|!_!PIEMği4F(:-H{>l nTycDڳ|2U C} Iℤs즦#2ـz͙[1\Ο1I<YP|ZI;D9Z Q{A@eatժZoэD̓d7|e(=j툞P@jXUĺ:V ~Ktk+$ k9HcP dy^Trg؜FAD,;S#2+t70lwż. vi>1r'ˑ:Wo巋sD3T@OQW*4 'u,u{7А2łIq:\U^1Tw ^VVPXD<7ё&˼ |/($jQ\ Q,{5=ٷ¢H92ӝ^f=rᝇO|Ge60peSt\oT%dkp24ʐIVL+t!GjOM7is\IR1Z}6xsw9?_Xh923]:C>j|ܶc Xx:vC֒2.%çN _X b+Pg4&~Ǒ7%P*$GYY>|ndw*OCfW(PbI8և~IF*BCYg1EuF(:ubsn,wxڪEp {u>s6jˬ*Ųޏ]=c=^gy6ůK|UUO]'r d}90#6\~Ş1ŷ74?x-1>^^؃:תd=18 KiJ,ժת6jP`%P88`YeADZ%ը",l+ZcE8X Y|f)Sקܿ#H|VΪc{]Ѓ)_,|h=/$|tgw=(~ևgH<1ořk/So4H%>=ؓ> ?/ȸY];* sU89.$^11 Mj74Z3f7n3<ɫf8.ȸ`5aa$ּhs>~|H%L?QcDs»XM|v6ϵYQzYP(pCŦtJA#=E#ʩOLО )E($j(T5yU9WP[F(ex$xѾܸ.T |҇ i*O9\bbeE4W)] #6vyn˻ֈosc;Ov1Xzުkyq2vQ?|Cˋw84{"k-ܠѾ@ I}ue1i5$"ӏ"OD桪l*ڹnl0LbJeP.c5`K 0RZ3t9n#j:z,zL~xk dyEU2/?m < |+O2u()ǽZq'C(|v Tz0jQB'=]WL{k8Vʽ@edYzFS- ,7s SQ"#zHjp{#滖 %v 1Q\aOx2pXoרvRu^S8(Q稒\%s "2aFl9)i@ʢ_kzS?.||X+ =}ǯf*1,csi+E,XE͹-s]LN9]h<"m_7`[I\[ta鿝 ,V_:6?tyw*ι2|_Es)I,P l;4"ȲuiDcr§*U/^E="XgT\f JU^ɺ]XPdWM\c 6$X[]9g8.A+Hk3ߢ:FrCŧ"8έevh kr!r-xYJ++T=|!<˰neqm.w {||{jqNAP60X"Ʉ>Z6UZWa9q,Wq[Y l9:($I2Tr+*aƷ✦"2E|}HIIH4ԣ:&4Wrpy!o;yڪD ·DE+*ևD>5 aD54i/\8Iz$I*Uz*rۙï_.J`idYF3hh>^}4%br{1cJ#aV!FIg]tj{7Uc*+9m>\);gX+~tp3DjVbZ  .tê#툰yk-ܿdMa=l:yI-3ƖnuCGp|V*$$D:*#d~(+JXki۾jO zZV?,<Qy1 ')%$*Ccn/O/ic,^%hgOpf@(*j]僧- n2>6,9M`aa(NNRtPT`X kۏ+BkY\XZG|ևmz.݅kP-ٟiI7[V?xX$,gMQȇ"Qcǎ?֚,=?KҹevmX}JY5+LʷZЉǷ9;٦3k@桬͈;j(79 cV 5 .ۧiZXc}VMX 諆=8^@o%%W1,..⬣U8zmi*/T-4Mɲ~O$g^pHrO:/1˺ƛ4M!=YUHε.s5I?ȶ[m=m:bK}=p ²[UOQv N쬳o"؜-v[7CCk-IEHZ=Il^Ve/4RVy{wl6IL1VE'MSz}1Eʊ; O:},s:65|WhƆf$"4M:sYk IDATv3݈O뜛uy ᤜgL)Imv7thv.r u4<*k`Dtd02`\pِ譠^ 9XsE 2B^R>vC F찳gjo<">&7_l (41lӡ޽l'qbgKsX^?|Ap1!Gd\3J;kJZo#ablFv 9uHq8(pj)j:Z+ 6[o3Vv(*weZ-4viD'_UQp#M$;v/yr&G) RGkunug%;ٖj#Q?πs<]*I9Jk1ׂ5{4!ы8dOo'r[ݷ_M=!*NBޠXXXۻWz"#2IYë$ڂ$exv o27_; Z$w~X$sE 9ŪU*$!r'nܸsvyoJ$$I) @f[,E k ~ K'dmTxr@V".Io7!;(@8Gc >.|?ӫ_ə-Q[qY+Y5g Y<Q`zyøT̞ɇϙQAl$e|y Cx0HMj--T9(ly] ExOPI0$$H_@i!2 '(rIzk |?!N>.zU?~U@h7n?}x Xg8o1M-٫s 0hzJ$2VzJ (hBW%&PE 2y^Unoo<^Ÿe7k,s F~s$ɢ6 cPAQ,Q !f)'49E{8՛ZDߪ<,yx9\}(FSS6y3S(p䲈 ,gQ>! I quue8XN^UVn೗uv >;Cf  IMٙ3z}Ճ 0i%[?6KtK_Dˎc}I@O/2ދyQ*Bt{t/9OF՛9>I[ u@jˣ2+(!a08ZYrMOߙjj+ן+*C$-\]$$* B'@zIH P眣Wz~yY ׍xggb #cʩ=$Ō'9RV |89>W&}#vt<]Λ:IK\zMWs͙(Bdy& :N`/Lܹǔž+J/T_ ? 58 `<ˌ߂q Ck9& Q!zJL3|y۴ φC|ݾ{a`bnfo 3NMD13t 4w&Z^8c7us..èjrZ/g H M2 Aze[tylgZnY18H>*F!)"^#zNq;OC"Εi I Gx3c r6"\1SL-fSZmL&D:$NyPݶ5?.B5ۆa,1E^`X ", qx L cFJ-q}^2%U'c rDp Y 0N b1&'oo(x$(5I`c!C%7x/0+t`1Qye!r?T_ҖQ%0d@7 t{'ÿ^Ȣ(zޜK>,WAH\jWޱhօa_& 98j0Iedk1I, 41\ R[L~|#.|4~<Ʒ^+%!1|#r%uuA }ժ&U cWf| \+5uN>p:AFc#r?h:Bmos\XVjVi%}@&(DDgx;"w^xg"Wc |`8|Ƥ3|vg{|`t|7z{'.+Vof!rU7ϓcA|qxdXP]35k%#ȶzXVV6Bz:PMC*_"wrH$?AaA!3$;( 9^ NKz)^FcT +Qsg;)DU0 ?k  ҧ_EMSF#5oBmvM`Knh"8l-Gګ΃e6)(p4!/П}_Ew)zzTU̫ àjNw}u A~1}i'f h g A8>c SUlD'0A+fЂ-QC$s}T f֒hm2clI=H= 2"FAqd-W럻a22g}Ȍ.H@k{NKS۶T2 "C"W-^vi(FbX4ڄn[e vM zHÀf<T;U Z_YZ[  `‰98>>U%vI`&N2cu@Lzݺ.@Uo.<0 M FDb۰n۰!Iv:lͣjPkWBdh^`+D` m ! X9y 7צK] sh"w_Rר?$ƻroY ۖ6h[{ zT[~+2*mdJn8!@-OY$0qss|nHk$@d=` Ǜ0.@Cډ44~Q@LV^:˩ϧ:z*w6꾞1vr:_mi< s rqsS: =WMaXm6t[ce VN Y^`|OK4lhTP-Mk]{qV+Ƽ<~W٧n+\I~t,.6iv:w ){8mKy/ F;o**&Y!O>tՂөl>U@yT~W=4;ח?*F뷁!Kz=յ*YM E!a~9>Sw}d*9FTgQ"d\h$?> t w3fzL'فu^ȳ Iٙ*DU(R|{a;ɶ|V,CR6>ND[C <٦ޘB =σG UrqC BƬyayT:BE;r8>Fka}0`#1Fd̩or?˲Xy^| /8W #I{.0*8z֡(Ľz~A hwaY,w"w%e9眗I00Y\TNvY$Ќʲ *f }P% H <+ф@;!֢Vy!h1_XcȲ ._ӑ]nqxސ}c`8!T'{C #`] {VRJS)@/TE־(dERҝX' h\A}*ALղ@Vy*g ]H뜑,K4)JTS!_jFD&K^ ;#]ؿCѦ7gq]6-wp8kB+ $D"^άpӍa"WRElik<9vm>Np8T/Pa* n2f^5=gU?g{, CٷJȝpKt"4MQ~͛9jׯ$]\Ea4IӴzz&Rx2X̯?zFf6Mvz4o7η5"e~GMh_kI~˿EM6yMT[=.<ۦަX[~`ݨmo tv,+~!>aYIb w2*{N`K.m{ip"P!FEVUo~9b7M^'G|?NyQWEHdGC\_=zԙ/qlwRL3[;h * Bd%:JEؖe_ fp`B/{;F 4  ĭx:1Žg0؈+0H0˿NϢܝU҆ccC>GwTy\]zLi!jK1]z`txř7o,ƕ/~ոzO.*[ ..Pk}փ_vP*ez$Yt в(0]Y %Y1^rā }^밞d+ -8vV`o"']&~,EH4Egn^ϊ"o.Hn?7GzlT [4 b'kYB% 8bx.J(J}\\ jJ2Vu][:VyњzAEQ, E% Ҕf҄ML[o65YP.\e|hASqR2j.߭cHui[H)IM{sa;Vҭ|'Yцv)NV88op*U*  ѡCzw]=? 4fG̕WIENDB`mailnag-1.1.0/data/icons/hicolor/48x48/000077500000000000000000000000001246564717200174215ustar00rootroot00000000000000mailnag-1.1.0/data/icons/hicolor/48x48/apps/000077500000000000000000000000001246564717200203645ustar00rootroot00000000000000mailnag-1.1.0/data/icons/hicolor/48x48/apps/mailnag.png000066400000000000000000000054071246564717200225100ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATh{\}?q3sgwv066k6(DE4"QHY%DPJ-M"jPP(ib9BM`YϾ<9c]Z`t4s= k-ΐ-h`Wq+GRԂ<睋oxE.|'ϮVld>:tgßlG9Z$|kFe9lxT<w{{Fwh}a㢜w%M l &ƚL 5mYy_T?Wِ8C#cτ/a% = !M^,(!nꥏ=xuBBkImgovW5k6&šѢ"0R -$ٹ^9~uw%j>B+Wi/ж'}+Cܦ0 4M:~OaX!L X9X 1vE1.;>, (޹s~ eَeT^:.TAv>~y @Q&vfq"AvARz  SmiPJ v ސ@&Lx&]EW>)~qj;?W#_$kk_A ޯ +,ZdBf Ɔ ]N49zz̬\hks-Bla02} T<pӝlK2W!LBm0$Z*\E`po#&Iָck_XmW\^5 xȲ v zaȳ/R5X]/lH YN+pYɀXkT& ߰ԉ:/[Ztx0^<}Ώ,`Iin8?'umH;[@ݍ( QԜ (DOSsz*$7<>_ѭ,˰^USi%{*<zt>[/יmgK秚)6O.kK^<܅gaO#9c8IM/9O駗-jrcqQamϞ=L~F=gG="QjYNE=%win8vfĤu'’w @P*x (\sDn/ITrQsv#6WpG1my -$ǬSoRpfDQ?.gS+ XkK [)I&-vJorTd/#-|*AquopRoBrր~tsMRk~Fnբ+-sW8iUJ8M"zCCCT'H ('gZ)N\lP+];R56}vh;|Нhܵ Hkמݨ8AX_m\i8j5X~pzg_&x[Ir.a1Rc&6l +ؔ4RAd+$#㺮%!TU`Ű"=E]vn:ɗ&0ᖚw06T`Ha[T3'[LǕ3knz<,j|@ev kwX<٫F:S .hVf)R~>o-˹*/>/[w22QIpRȌZ݆u<փRZFDU?C=R(5tgco0Rh sc0g{BEq][ nGRG׵Jl6?hxe99QVlVUw„~ic{[%%WeX9f`.|硔FkYn%p#KRjSCn+OE+1$+_*Ӓ+@3*APB\=#D׵Flъ !$JJrn#]ZMˊ,$ɒ7 \Xk MADQ-u@V.ZՂjE!(hr 0׵ 7-|%R8Q*tNSęEp@IؽI2:MZk\]vOd~!rg#)Q'{}1 c'`Oc=>{}1 c'`޵yE~$Bu3|7 2_QD?Bx?@O8΀`~rlw}v(c?0+yL09!sro#lv\{13ӆbl\jGVכzl7c? .M/YaY|BTC7:>Y3'~eKP.VIot  iOW:;B{'Qs 7|E@\j0@Y}WVe87>ZomsBTDgjSO4yn>S΃ ~!g!;S\^ Xp9i;z- Bϟo>O}6¿!K7j<793+nJ: pRc{̰+qNXk!Q@c`apcy\f sx ӟIXy`z` YG9@!?}mPڡYѣ Z5v&(!nj=̵7rL3<| umK9_v <Bv>@o"334G+x)aPy2xz]K9p$& |HGO5xs!ͅlh,ϟ^Y; 8+}m9GYDMq0] A]pyE)d@4=7CKmH΀-qD#lXŠ͌Xk8rC90-/ͅois[lt?|溔dŔ%ybrS~ՎG&78Ǖ^Ө֟v;SxW\7݋;/ _?fbT\^[z:\|tGEptsߝSnr:uHU0lUuȯ}߀kP\1נCoP|zuA$e-GgK:F!#AinD3d$[M7.xX‘WJJ~h̑s}u7m[/g-9Y,q*Q>ѤK ˫XضQH;ȡ*w٥W'%OBK^Bo7d3#^=c$RJ pNP1H[d>+[K6zh4nrr,B>˼Z+ >|2e2<Ԃ' =:xNxR K7JǷ/tz[H >|2XKeLBS陷(EQ@) b䞝 s P8gqe+s%kbc~֤I@( I2 KjlX|>t,!ZzŽ~_/9s=27\^)k+|p& 9KxGVn[ r@ȓbKR^-4I Gܫ٤ɸnsSkJq {o>‡]{;hEHt hZt#:xǕI'+OO8T|)[l>z[=|DBגPZw.9؊M|C=[ČkAifh4-w@C"!H;Z5Ěg݁\8?7_ƶ~N PUSR"0"4:s\XRIQ0\􉘇Wgͩɀ-3uvz啂':1|Y1ͳ'"sv8fo+=K(DTz$+(ڤb !qB"8!|r Z@;/n~K-V& v޿Ȃ8HOuVłLT!ւHᵜW2ڃ-r3] !LǎDđX^xR7~6 lµ|CAu3{ȵq#&(S4O?3fp`P R 5Zk Gڝr9ƒĸ͓׺%Oju6<8p0|Bt|O8ؐeYC0R',-BH, mq01 ?C(cfo#:Q%O=˯q3n#$Rh u@EazeNgMs)s|t%>~@S:?K-Zbj<~$$BsBƫ󹷌Xc '5@L3FDu >B$ =Oü*/R:Ҁ_4>ѧK22.M$2L A@qm1U/ ]ؽ {5/silBBC 3'"U.-|Bbߎ/RRZPpXwh VPqDz03zI):S8"T`[AC g>$UI:QB˜8LΆ=>p8'EC3 >P.|Jf=J yXD*B￝fG95`f,`Jî5(QFh%ZpBAD#FKbKܱA,R \䢘fL7+sg|#-o$-'7>W;ELXbkj!}gwL5s#I7Kh%t0 Ð@k䧀}>5JkpnXWl;ڄJ)R(%Q牸{:\S,<\X6O<~4$ $9&b/?;srWJ9ђ-\{ 5#9xo´Xblk$ i1A G3:^)pر.=TG /DJAe$+\MBpC/`uo8w}'9PUΑpX0ah1ah4Hdvk|װZʲ%XJ(DJT$av., e\*-ՌhpVD8ndo8XR9Rp"`,?یICh=> j >r4c :բ6sHctU4pǦmٓ <m '&CƳEZIDQDE/(]kfs^GiJY57]%+6p:LԦjU q zN^_kkoym>_LAI)>8,9:b DQDkb}Ds+$씨[&9G#bz=8^he`0Z8aRt="tr3)1V,KϽ&H_W PIOFj7h5db(9Zx[z1Lez#arMl9 CQbm|b%~$Zz4ĀAˆpzY^ 7zPTȉ`41߳ CV4)EQ=^ode}ɻC-0s_t(鉨6~QkJQyT)?S?|K#I &m)gWb.-,Xs!w{&BczVqBPo M{vfrC uQieA,ˆy@JE&C;UNƆNk8:@7y^ dB`@S 8]4,g> 9fS4B#"N&]F$iDqT$bra! %`$욀,QJ[a ;ܷ , <1 дZ-p=( jA{Uu*.fL g#f3Q "dCXf2UaZ-0sR5nŝ$aXkQJMwk$IRWE ql垵 k%%AJfKN[qu@/Xkͣ3)'.FLPIJ~)u5%%,ok;E-J݌Z{ҼL8@վf(sC(V(Kiʪ3⾸`H%)0g dEJNg 7Pe70jj:ڸ6YQfjxI)98h )(e&55ZhBITߩa)i6[t:J6,Ջ@-02X/1Q%nlchsk@unU3?ROOU$yEԨ}V‚Z)n?3*7_^]%a{YI5Y?ZcQ$Y'2G6"EZ"UYvHYu-Z#uRfA']b*7Y:ƬfTkWFhqHƯU#ʸV:]U+Jr*jJ!Y CW5vOUX(B*bVIYsv$'jS)BIse8eCDq~6 G۽%`)eZj7c! C _VcIdMy/Z}a%d}nof! C] AӇZM8_p`b_{y6};u"DJ[JGYбYjtd!uW,a k( vzJݎh4X#wCf TRJ9ca *F'=w#PZ-_ g'{6df:"\pnazuG;w FcsuϳFaLW ?^;,MSz>֮;ګ Tj\ ]!h6 |f)DYF9xY9 >⾛EEۼ 2]ZquE.ٍu%ZOJJA0vzkicj4<'˲6# av_䛟wiSR./?6yAI QNO^n%WzTnI[F~ PcGwhnSn9듌~Wc+ClN}Ѷٶ;m͂ :>b/  B6F>דr6q 9[w;myS ;V[M5!{qVZB8e9x ʏ c'`Oc=>{}1 c'`Oc=;R_IENDB`mailnag-1.1.0/data/mailnag-config.1000066400000000000000000000011201246564717200167770ustar00rootroot00000000000000.TH MAILNAG-CONFIG "1" "Feb 2015" "Mailnag 1.1.0" .SH NAME mailnag-config \- Mailnag setup utility .SH SYNOPSIS \fBmailnag-config\fP .SH DESCRIPTION \fBmailnag-config\fP is a utility 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). mailnag-1.1.0/data/mailnag-config.desktop000066400000000000000000000005471246564717200203240ustar00rootroot00000000000000 [Desktop Entry] Name=Mailnag Configuration Name[de]=Mailnag-Konfiguration Comment=An extensible mail notification daemon Comment[de]=Ein erweiterbarer Mail-Benachrichtigungs-Dämon Keywords=mail;notify;notification;config;settings;preferences; Exec=/usr/bin/mailnag-config Icon=mailnag Terminal=false Type=Application Categories=Network; StartupNotify=false mailnag-1.1.0/data/mailnag.1000066400000000000000000000014041246564717200155410ustar00rootroot00000000000000.TH MAILNAG "1" "Feb 2015" "Mailnag 1.1.0" .SH NAME mailnag \- an extensible mail notification daemon .SH SYNOPSIS \fBmailnag\fP .SH DESCRIPTION \fBmailnag\fP is a daemon program that checks POP3 and IMAP servers for new mail. On mail arrival it performs various actions provided by plugins. It comes with a set of desktop-independent default plugins for visual/sound notifications, script execution etc. and can be extended with additional plugins easily. .PP Note that you must first run \fBmailnag-config\fR to setup Mailnag. .SH SEE ALSO .PP \fBmailnag-config\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). mailnag-1.1.0/data/mailnag.ogg000066400000000000000000000356361246564717200161730ustar00rootroot00000000000000OggSaSvorbisOggSa5=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$ &=;RKT<̝?Lvg5kl ~iyM!:k#-%, @H>H]f"o˝^MʡWC eMHo8yv#{Nb:#qG̚5>ܡ0CAr%Ar5GnLNl:Z:.mߋ_9+[՚56so1S+91{?EN3sg0 3-B8AX2$Ó_ք>>)$~E?#$NvAκ* HwHY:'zn1+RI'k+\ubT|B_~ >d MзA8a`# v5)v<|Wo rˢˆFo[B׸ ~=#} Lȅ9q6'Z^"9܋65+ ??'B(!>Kfc]&NZ0+8LHɗ"Q3J$OĐ)BgokCMew--ToG̻v"D3X] ˡ6e .ҕuN|vqFk8# vLkR?gΠ>G[o9n|߯!ϭhMYe8kZXBB.H母QF 5/d56A䙧pBB MqəCOh>&K vG2֜~:8'N໰iHV,wfh? Q-IP7a/Zr :;,ǀ?@VȬ3?+-D:!J:F,<ԢIo*HP3ƠM *"Dk(gVnOnOG,/Án<\_Ik5h%'FǸBK@xX_[XOFwCk5ּz;&5ElϑK{8qm%J.rXk1Zc/A^MO!oKN*#.}=<2k!Ap̼?ߦ&]&YtLj͸71ۮvQ-pzǹ-BY3L(5F`>_ p)beWw\|Ub#MZ;!K 2-pD~3shMȖ>o}-#Kem1f`oo@83[R:fA3N;!d6R yw> ~vyxu5%d.l[O6a|b8kI69} ܕ=8{+WdSF;g30C3!@^ğ T]dfHT3ZtN3H_: t6 5|*߻'1fV tB(:ʝH"TR@"!-@ #V1-XInmF{eX ! ݖ&gz|7e1# ]7 @F..s|BT.lmk,Z[P"B _F-ut z0HXY)q6wZKeݧ:E"7H86|/ߎѕh6*..\My#M=++)߽8$ڤ#cƘ i3$d3B";ߺ0$ջk CJaf6y-X2um[TY Հ"j#ZOU8zn-8bHNuÁE߄Zd[NSߢuGV<.; -Go<ᐍC:çۼp#k1fpVʝB59y;w~6K)O[vy2|~3b}nO ifP}5 ,@^~1ҕvJPƟ'iExg OaM12g?b釿Dzqۭ(̑Rxs] 7Y~V\(h8ۇAwRTh/i`F c4$OH eF9q ifɴ@kRߗBik 4ijRO Vn?N&Bi{Pwlqf!.>xߩI$ x#M|iIӔ,&H} /#L;`iSb"kcJĆ:w2WdKHO~qq<k7c Yv.ҿ˷VI23/[xP;zJU3VO+DUh[j9$Zݞ'.u̻1RJTxbr/(S("Ic}%1T%ϾD 5 O-^CvRՌlrjn{WSq~HKV}'c=e-h+@hXZ;LsGMbfֈXR?Meܞc\a&2.XkWj4RJwZk2LgDJ1IުUu+=wvn-X _ye]ԩ C]}Z̄ӯlk-Z;_jϯ+hEg_/}$BA8hȹ,PyWEWkXk` ԁ =]#Iӌ4H,͈8NH$N,S'Y-;J# z|B'p'1߽w-_S-6./g|e+!xo:4=SB,;sme#*sJQhfVZđY;2lzǗou\MbW -E~"Fk]j4K4MSG4M D9* MŇo2׼^ʀ[|whMl>KC{V@KZ5GVZkYjxv~BVhaĐwyO$ }0gXr&nD9Hr[bu3-r- #[;\h]ŞZJ)mruF2$!MSQLhn5O2_xwXJ\I PǛ e|ܐx>cn4[V:=s-#q4# "4 ZrBƙMEbkDmRWb4gZҏVzg_<[/5%WnS ΣveNi㲉Ys= q i1scB۝[C=ޤf_sݑB[g;TkH>sCneaJ)t,-h%z bt 9< y&-VF.#эe;rc0d#E}7yڕ[a8жJbP9D$ NYJ$ a&$! bDOYm[<{mdN0 ݦXHmZ y>ـ¿+_P}LV9Y&F"&rij̓o~~U esLB.%i 2I=MV\V'HObر=OjrfqD[v>]t&0x`O(L_|v0c4KʋD`DF_do\\" ?/=)9.,e|)\%Gs&Qph(rn{*$47(i8IPR ! z'\aFt"KI?Xs..|&Q3ϟribOejUw'f:Tnvy%H_Yܹ!etsRBpbΧ\L,:(yA#RH,VZ̐Vc8bxX KYYwBe HQ"@IJIT>T*cE*Sq?x(2`'3V"\ y# b@ohk ߼8,#0*lÓG񐇒 v(Vňg[JhC[|xfBE g<|_>)S٢t.geIXuǦ:;Ah`lq?+[4EJ)X`x55@ %kݑWS~LCH\nϟTYAQLưF8Q|txR*ɣ#hF_/6uh݉|C҈4<spxBIGd!B82k+9bk1c.'7Lniڭ0 nb euFf$I4yTp8$Wg%aG@"x#!8:U }F/ !&J+ Yi)<cy gKe\N 6>ϣ/89a FOO(ʓc]뱕uox'2Z@};qb9p: euťպ8ɥJ4A^a? gflI[Wu3?el%R|N{|!w"[o&Ul𙟬qm%/ܮX# u}lVQJh%}?$~mVBV vrX,J]4cx @)/w:HF@&>%Z 4<;YZWU[gvY./eżdc^؅'%;_7dVrgug ;7IDAT?0ҼN!:M [D% ِ˗^S/:$37qԽf9>v 9`<- T.Ja$RZd.dz^rN:5$NX܅}pd1l'H! t;x˱ei Vźd3/I-_dXZN(7<;EW`~Ls8p|6XCVi5a"W٧YJ~%HB뢮إH\^ Wnm\iE4A"$V&c/H';Ft;ؠ@\dƬv<ő=)p?eTh<Ȥ~= .Z$|s?cØ΋CA'%k*+?fpghʞh0.l43gN7 pCd[p[wY>wn 7(.D*ɏ!:\|f3 g\%v_:ϛ+؇/g<^">kWpDޗ#RnV.+n0Ar1ӲlZs/&|U(r~13/Y͔ 1f-g@"/2.nƞ}W.*w<'0Z`0 IRFqWNXe<+Zg.D)|'RL;8\.dm [9pm7,Zo7|q/i#9|\54L]Hz~ޑny o#f;yq#/"DH*p6wElA'}#twVJΔ' o6z2orYP`a YɴFH<4%Fɬ5xء,x3=:>\Z]o 5ޕstWj++O8diR90$; @fBQK\))s\pQk=6Ma"}!t'}Z&F|څ%ILZif0V6.Zm"W ( :iByzGxWWZSz)~/~:Iw_= -Uݥl+N/X2iMV8HF#,pQ+,cE8a!kN(mp4 GQ`0 X,{̔yyc@eDQ[n?~4XB|Zg$7u{=RmMZӯyv1+k0dA BhjV(%tkztf:DØ,KP~*:Ћ Z_> ~M  ~Ng =tkY]]=WJjH(<)qLp!ÇR4-~4d%##t7l-TRx}݂7nB꟧贻 N)epoV~ ?8m-|SL~Ø6n\Xˑ2WuTx`HEWvBR [wIHWdUL04#k3Fg b8+R9}4c9I2W6-Q 7M;ڨGɒ:ﺴ0BW4ye0$]oekEB!$)DkKIAcxKQ97Xq~%|B]/M_m曂x4̵<EQdJUs9M/oJY2[&BiZawB( v*aDfaa@wH\^kJٲ|+]+%68~eut8)Y]᧟MG4=v{r2Rݢy1h)ebβThNr1"rSd69<2(~SrN'Bؗ,BiV Z+tv[3?ÅxgUNk\Q3[.CHW*OrRKwͱn{=N8T\E7Ngju47;K:Y0 6r&[ϖYo7*;(%sʋxnFZި\g+[[p {j(j/ Q[-t {&fyβ\&^bO5t{f8;-cdNڝa2FIB$ -ڱ'SU1G̸ MvjBћqȼ$B X(Hs(Bra32 s2[ i233H!iZ,&1Z4Oө3'e4^cZOr޹I6ȭ54'!(Xʌ tk!(W*@X][%m-Mo-Yup;i+ϣ ƺp0k?̨:&eGAj MVU?@d7w*OGtfHYN1wz3Bq3(Zߋ̥>%0;+{=yw5 NVIE/$@ن`pyjZӚWb"V~[i0F]BJIK[P?EB=o`[^n1ύ֔2&O7Du&z'u*ˎB{W+Ӫ벶@i$vVz|7 AnWJYְoc.:WXo8zZOctq2hZL$](<);}fa (oΘxЬ4S~X^^)KeQm霩V+gBi:MXw<=V9/¼l4Nzɓb}i0Y6wx.ZH֌*=ߧ7˧?ykvy+Zp7 ǮhtRTQg/-]z 62pRPJFEv_bvvCVeht&Iaa6UmKXCzc#lX{ZkFY=8)c{v.ʲlbzT& TC)[p/]r(Bhƅ[QBZSUyո:6Q~0]N}>ybaaޅm&?n~b: /j6$iZ_N-$(r ylbj?ȳg|۩;y$ShAQ]؃4_1h!5j0j&;.?vL0a\N7QchrF86: 09N|\֠]zGיeQ*o5klڲ2Ib7s` nT;Ѓz=/jֺδi2F7y-,g[ӟ!G͡HH$Lқ،4H-y}?b[=ը1k }˟{w,H f$N-p\ݵ{n~4)eޔڜEֹX 6| yoDhSY{&3Nb ucxPHm>LD*DZ݋m;.s#qK]mBbb vJ{]@]Ve7{ȸl=iH1^b;E1l{Slw;vL{ܶe.w[ꪥ|=lU&lՂnnt}ًϿnMaSajyʙ곯7J$wဝe `c=Uz}ս"\Fdؒ ~ͻi6ъo;}Ny^ 4KS{kIx]ϷJf&ajI^y/Z7~ֱ2 Ioo{ZnaLlvHr%{S/AُJZ~c'D.+kwAö $л 2XB?x.42jBWKW ]c_&t}5jBW ]c_&t}5jBW ]c_&t}5jBW ]c_&t}5jBW ]c_&t}5jBW ]c_&t}5jBW ]c_&t}3IENDB`mailnag-1.1.0/data/plugin_dialog.ui000066400000000000000000000055741246564717200172370ustar00rootroot00000000000000 False 5 Plugin Configuration False True dialog False vertical 18 False end gtk-cancel True True True True True True 0 gtk-ok True True True True True True 1 False True end 0 button_cancel button_save mailnag-1.1.0/gen_locales000077500000000000000000000010201246564717200153310ustar00rootroot00000000000000#/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 mailnag-1.1.0/gen_po_template000077500000000000000000000010731246564717200162300ustar00rootroot00000000000000#!/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 mailnag-1.1.0/mailnag000077500000000000000000000103751246564717200145030ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # mailnag # # Copyright 2011 - 2014 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. # from gi.repository import GObject, GLib from dbus.mainloop.glib import DBusGMainLoop import threading import argparse import logging import logging.handlers import os import signal from Mailnag.common.config import cfg_exists from Mailnag.common.dist_cfg import APP_VERSION from Mailnag.common.utils import set_procname, shutdown_existing_instance, fix_cwd from Mailnag.common.subproc import terminate_subprocesses from Mailnag.common.exceptions import InvalidOperationException from Mailnag.daemon.mailnagdaemon import MailnagDaemon PROGNAME = 'mailnag' LOG_LEVEL = logging.DEBUG LOG_FORMAT = '%(levelname)s (%(asctime)s): %(message)s' LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' def cleanup(daemon): event = threading.Event() def thread(): if daemon != None: daemon.dispose() terminate_subprocesses(timeout = 3.0) event.set() threading.Thread(target = thread).start() event.wait(10.0) if not event.is_set(): logging.warning('Cleanup takes too long. Enforcing termination.') os._exit(os.EX_SOFTWARE) if threading.active_count() > 1: logging.warning('There are still active threads. Enforcing termination.') os._exit(os.EX_SOFTWARE) def get_args(): parser = argparse.ArgumentParser(prog=PROGNAME) parser.add_argument('-q', '--quiet', action = 'store_true', help = "don't print log messages to stdout") parser.add_argument('-v', '--version', action = 'version', version = '%s %s' % (PROGNAME, APP_VERSION)) return parser.parse_args() def init_logging(enable_stdout = True): logging.basicConfig( format = LOG_FORMAT, datefmt = LOG_DATE_FORMAT, level = LOG_LEVEL) logger = logging.getLogger('') syslog_handler = logging.handlers.SysLogHandler(address='/dev/log') syslog_handler.setLevel(LOG_LEVEL) syslog_handler.setFormatter(logging.Formatter(LOG_FORMAT, LOG_DATE_FORMAT)) stdout_handler = logger.handlers[0] logger.addHandler(syslog_handler) if not enable_stdout: logger.removeHandler(stdout_handler) def sigterm_handler(mainloop): if mainloop != None: mainloop.quit() def main(): fix_cwd() mainloop = GLib.MainLoop() daemon = None set_procname(PROGNAME) GObject.threads_init() DBusGMainLoop(set_as_default = True) GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGTERM, sigterm_handler, mainloop) # Get commandline arguments args = get_args() # Shut down an (possibly) already running Mailnag daemon # (must be called before instantiation of the DBUSService). shutdown_existing_instance() # Note: don't start logging before an existing Mailnag # instance has been shut down completely (will corrupt logfile). init_logging(not args.quiet) try: if not cfg_exists(): logging.critical( "Cannot find configuration file. " + \ "Please run mailnag-config first.") exit(1) def fatal_error_hdlr(ex): # Note: don't raise an exception # (e.g InvalidOperationException) # in the error handler. mainloop.quit() def shutdown_request_hdlr(): if not mainloop.is_running(): raise InvalidOperationException( "Mainloop is not running") mainloop.quit() daemon = MailnagDaemon( fatal_error_hdlr, shutdown_request_hdlr) daemon.init() # start mainloop for DBus communication mainloop.run() except KeyboardInterrupt: pass # ctrl+c pressed finally: logging.info('Shutting down...') cleanup(daemon) if __name__ == '__main__': main() mailnag-1.1.0/mailnag-config000077500000000000000000000032201246564717200157350ustar00rootroot00000000000000#!/usr/bin/env python2 # -*- coding: utf-8 -*- # # mailnag-config # # Copyright 2011 - 2014 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 subprocess from gi.repository import Gtk from dbus.mainloop.glib import DBusGMainLoop from Mailnag.common.utils import set_procname, shutdown_existing_instance, fix_cwd from Mailnag.common.dist_cfg import BIN_DIR from Mailnag.configuration.configwindow import ConfigWindow def main(): fix_cwd() set_procname("mailnag-config") confwin = ConfigWindow() Gtk.main() if confwin.daemon_enabled: try: # the launched daemon shuts down # an already running daemon print "Launching Mailnag daemon." subprocess.Popen(os.path.join(BIN_DIR, "mailnag")) except: print "ERROR: Failed to launch Mailnag daemon." else: DBusGMainLoop(set_as_default = True) # shutdown running Mailnag daemon shutdown_existing_instance() if __name__ == "__main__": main() mailnag-1.1.0/po/000077500000000000000000000000001246564717200135555ustar00rootroot00000000000000mailnag-1.1.0/po/cs.po000066400000000000000000000127361246564717200145330ustar00rootroot00000000000000# 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: 2015-01-31 21:20+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Nepojmenovaný" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(a {0} dalších)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Povoleno" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Název" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Odstranit tento účet:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Poštovní účet" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Název účtu:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Uživatel:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Heslo:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Server:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag - Nastavení" #: data/config_window.ui.h:2 msgid "General" msgstr "Obecné" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Účty" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/po/de.po000066400000000000000000000154621246564717200145150ustar00rootroot00000000000000# 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: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2015-02-01 12:15+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Unbenannt" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "Ubuntu Unity" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "Zeigt neue Mails in Ubuntu's Messaging-Menu an." #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "Maximale Anzahl sichtbarer neuer E-Mails:" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "GNOME Online Accounts" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "GNOME Online Accounts Integration." #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "DBus-Dienst" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "Stellt Mailnags Funktionalität durch einen DBus-Dienst bereit." #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "Klangbenachrichtigungen" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "Spielt einen Klang ab, wenn neue E-Mails eintreffen." #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "Benutzerskript" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "Führt ein benutzerdefiniertes Skript bei E-Mail-Ankunft aus." #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "Absender" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "Betreff" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" "Das folgende Skript wird immer dann ausgeführt, wenn neue E-Mails " "eintreffen.\n" "Mailnag überreicht die Anzahl der neuen Mails an dieses Script,\n" "gefolgt von %s Paaren." #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "Spamfilter" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "Filtert unerwünschte E-Mails heraus." #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" "Mailnag ignoriert E-Mails, die mindestens eines der \n" "folgenden Wörter im Betreff oder Sender enthalten." #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "LibNotify-Benachrichtigungen" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "Zeigt eine Benachrichtigung an, wenn neue E-Mails eintreffen." #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Benachrichtigungsmodus:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "Anzahl der neuen E-Mails" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "Zusammenfassung der neuen E-Mails" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "Eine Benachrichtigung pro neuer E-Mail" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(und {0} weitere)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "{0} neue Mails" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "Neue Nachricht" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Als gelesen markieren" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Kein Betreff" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Aktiviert" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Name" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Dieses Konto löschen:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "optional" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "Anderer (IMAP)" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "Anderer (POP3)" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "E-Mail-Konto" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Push-IMAP aktivieren" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "SSL-Verschlüsselung aktivieren" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Kontoname:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Kontotyp:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Benutzer:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Passwort:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Server:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Port:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Ordner:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag-Konfiguration" #: data/config_window.ui.h:2 msgid "General" msgstr "Allgemein" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Konten" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "Plugins" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" "Mailnag - Ein erweiterbarer " "Benachrichtigungs-Dämon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "und Mitwirkende." #: data/config_window.ui.h:8 msgid "Add Account" msgstr "Konto hinzufügen" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "Konto löschen" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "Konto bearbeiten" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "Plugin bearbeiten" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "Plugin-Konfiguration" mailnag-1.1.0/po/es.po000066400000000000000000000152051246564717200145270ustar00rootroot00000000000000# 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: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2015-02-02 13:24+0000\n" "Last-Translator: Adolfo Jayme \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: 2015-02-07 18:24+0000\n" "X-Generator: Launchpad (build 17331)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Sin nombre" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "Ubuntu Unity" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "Muestra los mensajes nuevos en el menú de mensajería de Ubuntu." #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "Cantidad máxima de mensajes visibles:" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "Cuentas en línea de GNOME" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "Integración con las cuentas en línea de GNOME." #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "Servicio DBus" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "Notificaciones con sonido" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "Reproduce un sonido al llegar un mensaje nuevo." #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "Secuencia de órdenes de usuario" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "Ejecuta un «script» definido por el usuario al llegar un mensaje." #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "remitente" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "asunto" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "Filtro de correo no deseado" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" "Mailnag ignorará los mensajes que contengan por lo menos\n" "una de las palabras siguientes en el asunto o remitente." #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "Notificaciones con LibNotify" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "Muestra un cuadro emergente al llegar mensajes nuevos." #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Modo de notificación:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "Contador de mensajes nuevos" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "Resumen de mensajes nuevos" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "Una notificación por mensaje nuevo" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(y {0} más)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "{0} mensajes nuevos" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "Mensaje nuevo" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Marcar como leído" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Sin asunto" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Activado" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Nombre" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Eliminar esta cuenta:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "opcional" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "Otro (IMAP)" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "Otro (POP3)" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Cuenta de correo" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Activar Push-IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "Activar cifrado SSL" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Nombre de la cuenta:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Tipo de cuenta:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Usuario:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Contraseña:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Servidor:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Puerto:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Carpetas:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configuración de Mailnag" #: data/config_window.ui.h:2 msgid "General" msgstr "General" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Cuentas" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "Complementos" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" "Mailnag: un servicio de " "notificación de correo ampliable.\n" "© 2011–2015 de Patrick Ulbrich\n" "y colaboradores." #: data/config_window.ui.h:8 msgid "Add Account" msgstr "Añadir una cuenta" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "Eliminar la cuenta" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "Editar la cuenta" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "Editar el complemento" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "Configuración de complementos" mailnag-1.1.0/po/eu.po000066400000000000000000000131301246564717200145240ustar00rootroot00000000000000# 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: 2015-01-31 21:20+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Izengabea" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Jakinarazpen-modua:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(eta {0} gehiago)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Markatu irakurrita gisa" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Gairik ez" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Gaituta" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Izena" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Ezabatu kontu hau:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "aukerakoa" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Posta-kontua" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Gaitu Push-IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "Gaitu SSL zifratzea" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Kontu-izena:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Kontu-mota:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Erabiltzailea:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Pasahitza:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Zerbitzaria:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Ataka:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Karpetak:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag-en konfigurazioa" #: data/config_window.ui.h:2 msgid "General" msgstr "Orokorra" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Kontuak" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/po/fr.po000066400000000000000000000155161246564717200145340ustar00rootroot00000000000000# 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: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2015-02-01 12:17+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" "Language: \n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Non nommé" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "Ubuntu Unity" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "Affiche les nouveaux mails dans le menu des messages Ubuntu." #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "Nombre maximum de mails visible:" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "Comptes en ligne GNOME" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "Integration des comptes en ligne GNOME." #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "Service DBus" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "Rend disponible les fonctionnalités de Mailnag via un service DBus." #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "Notifications sonores" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "Joue un son lors d'un nouveau mail." #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "Script Utilisateur" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "Exécute un script défini par l'utilisateur lors d'un nouveau mail." #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "expéditeur" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "objet" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" "Le script suivant sera exécuté à chaque nouveau mail.\n" "Mailnag transmet le nombre total de nouveaux mails à ce script,\n" "suivi par les couples %s." #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "Filtre spam" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "Filtre les mails non désirés." #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" "Mailnag ignorera tous les mails qui contiennent au moins \n" "un des mots suivants dans l'objet ou l'expéditeur." #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "Notifications LibNotify" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "Affiche une popup lors d'un nouveau mail." #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Mode de notification:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "Nombre de nouveaux mails" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "Résumé des nouveaux mails" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "Une notification par nouveau mail" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(et {0} de plus)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "{0} nouveaux mails" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "Nouveau mail" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Marquer comme lu" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Sans objet" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Activé" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Nom" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Supprimer ce compte:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "facultatif" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "Autre (IMAP)" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "Autre (POP3)" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Compte de messagerie" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Activer le Push-IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "Activer l'encryptage SSL" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Nom du compte:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Type de compte :" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Utilisateur:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Mot de passe:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Serveur:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Port:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Dossiers:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configuration de Mailnag" #: data/config_window.ui.h:2 msgid "General" msgstr "Général" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Comptes" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "Plugins" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" "\"Mailnag - An " "extensible mail \"\n" "\"notification daemon.\\n\"\n" "\"Copyright (c) 2011 - 2015 Patrick Ulbrich\\n\"\n" "\"and contributors.\"" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "Ajouter un compte" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "Supprimer le compte" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "Modifier le compte" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "Modifier le plugin" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "Configuration du plugin" mailnag-1.1.0/po/gl.po000066400000000000000000000147051246564717200145260ustar00rootroot00000000000000# 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: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2014-10-05 08:38+0000\n" "Last-Translator: Marcos Lans \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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Sen nome" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "Número máximo de mensaxes visibles:" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "Servizo DBus" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "Expón a funcionalidade Mailnag mediante un servizo DBus." #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "Notificacións con son" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "Reproduce un son ao recibir un novo correo-e" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "Script do usuario" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "Executa un script do usuario ao recibir algún correo." #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "remitente" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "asunto" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" "O seguinte script executarase cando se reciba correo novo.\n" "Mailnag pasa o reconto total dos novos correos a este script,\n" "seguido por %s pares." #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "Filtro de spam" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "Filtra os correos non desexados." #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" "Mailnag ignorará os correos que conteñan alomenos \n" "unha das seguintes palabras no asunto ou no remitente." #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "Notificacións LibNotify" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "Mostra unha xanela emerxente ao recibir correos novos." #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Modo Notificación:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "Total de novos correos" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "Resumo dos novos correos" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "Unha notificación por cada novo correo" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(e {0} máis)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "{0} novos correos" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "Correo novo" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Marcar como lido" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Sen asunto" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Activado" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Nome" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Eliminar esta conta:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "opcional" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "Outro (IMAP)" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "Outro (POP3)" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Conta de correo-e" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Activar Push-IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "Activar cifrado SSL" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Nome da conta:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Tipo de conta:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Usuario:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Contrasinal:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Servidor:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Porto:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Cartafoles:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configuración de Mailnag" #: data/config_window.ui.h:2 msgid "General" msgstr "Xeral" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Contas" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "Complementos" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "Engadir conta" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "Retirar conta" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "Editar conta" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "Editar complemento" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "Configuración do complemento" mailnag-1.1.0/po/he.po000066400000000000000000000132311246564717200145110ustar00rootroot00000000000000# 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: 2015-01-31 21:20+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "ללא שם" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "אופן התראות:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(ועוד {0})" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "סימון כנקרא" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "ללא נושא" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "מאופשר" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "שם" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "מחיקת החשבון:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "אופציונאלי" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "חשבון דוא\"ל" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "איפשור דחיפת IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "איפשור הצפנת SSL" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "שם חשבון:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "סוג חשבון:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "משתמש:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "סיסמא:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "שרת:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "יציאה:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "הגדרות התוכנה" #: data/config_window.ui.h:2 msgid "General" msgstr "כללי" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "חשבונות" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/po/hr.po000066400000000000000000000130201246564717200145220ustar00rootroot00000000000000# 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: 2015-01-31 21:20+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Neimenovano" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(i još {0})" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Omogućeno" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Ime" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Izbriši ovaj korisnički račun:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "E-mail korisnički račun" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Naziv korisničkog računa:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Korisnik:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Lozinka" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Poslužitelj:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag Konfiguracija" #: data/config_window.ui.h:2 msgid "General" msgstr "Općenito" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Korisnički računi" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/po/it.po000066400000000000000000000145721246564717200145420ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2014-08-03 13:52+0000\n" "Last-Translator: Pedro Beja \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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" "Language: Italian\n" "X-Loco-Source-Locale: it_IT\n" "X-Loco-Parser: loco_parse_po\n" "X-Poedit-SourceCharset: UTF-8\n" "X-Loco-Target-Locale: it_IT\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Senza nome" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "Massimo numero di mail visibili:" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "Servizio DBus" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "Espone le funzioni di Mailnag tramite un servizio DBus." #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "Notifiche audio" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "Riproduci un suono all'arrivo di nuova posta." #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "Script utente" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "Esegue uno script definito dall'utente all'avvio." #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "Mittente" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "Oggetto" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" "Lo script seguente sarà eseguito all'arrivo di nuova posta.\n" "Mailnag passa il numero di nuove mail allo script,\n" "seguito da %s coppie." #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "Filtro spam" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "Filtra le mail indesiderate." #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" "Mailnag ignorerà le mail contenenti almeno una delle \n" "\n" "seguenti parole nell'oggetto o nel mittente." #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "Notifiche LibNotify" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "Mostra un popup all'arrivo di nuova posta." #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Modalità di notifica:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "Numero di nuove mail" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "Riassunto delle nuove mail" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "Una notifica ogni nuova mail" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(e altri {0})" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Contrassegna come \\\"già letto\\\"" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Nessun oggetto" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Attivo" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Nome" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Eliminare questo account:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "opzionale" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "Altro (IMAP)" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "Altro (POP3)" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Account email" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Attiva il protocollo Push-IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "Attiva il protocollo crittografico SSL" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Nome dell'account:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Tipo di account:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Nome utente:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Password:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Server:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Porta:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Cartelle:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configurazione di Mailnag" #: data/config_window.ui.h:2 msgid "General" msgstr "Impostazioni generali" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Account" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "Plugin" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "Aggiungi account" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "Rimuovi account" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "Modifica account" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "Modifica plugin" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "Configurazione plugin" mailnag-1.1.0/po/ja.po000066400000000000000000000132241246564717200145110ustar00rootroot00000000000000# 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: 2015-01-31 21:20+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "無記名" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "通知モード:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(他 {0} 通)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "確認済" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "件名無し" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "使用する" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "名前" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "このアカウントを削除:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "省略可" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "メールアカウント" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Push-IMAP を使う" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "SSL暗号化を使う" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "アカウントの名前:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "アカウントの種類:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "ユーザー:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "パスワード:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "サーバー:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "ポート番号:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "フォルダ:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag 設定" #: data/config_window.ui.h:2 msgid "General" msgstr "一般" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "アカウント" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/po/ko.po000066400000000000000000000127451246564717200145370ustar00rootroot00000000000000# 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: 2015-01-31 21:20+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "이름 없음" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(그리고 {0}개 더)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "활성화 됨" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "이름" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "이 계정을 지웁니다:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "메일 계정" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "계정이름:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "사용자:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "암호:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "서버:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag 설정" #: data/config_window.ui.h:2 msgid "General" msgstr "일반" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "계정들" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/po/mailnag.pot000066400000000000000000000123401246564717200157110ustar00rootroot00000000000000# 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: 2015-01-31 21:20+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/accounts.py:49 msgid "Unnamed" msgstr "" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "" #: data/config_window.ui.h:2 msgid "General" msgstr "" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/po/nb.po000066400000000000000000000127411246564717200145210ustar00rootroot00000000000000# 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: 2015-01-31 21:20+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Ikke navngitt" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(og {0} flere)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Aktivert" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Navn" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Slett denne kontoen:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "E-post konto" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Kontonavn:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Bruker:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Passord" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Tjener" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag konfigurasjon" #: data/config_window.ui.h:2 msgid "General" msgstr "Genrelt" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Kontoer" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/po/pl.po000066400000000000000000000137731246564717200145430ustar00rootroot00000000000000# 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: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2014-07-16 19:54+0000\n" "Last-Translator: Piotr Filipek \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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Anonimowy" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "Maksymalna ilość widocznych wiadomości:" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "Usługa DBus" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "Dżwięk powiadomienia" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "Odtwarzaj dźwięk kiedy przychodzi nowa poczta." #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "Skrypt użytkownika" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "nadawca" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "temat" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "Filtr spamowy" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "Powiadomienia LibNotify" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "Pokaż okno kiedy przychodzi nowa poczta." #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Tryb powiadamiania:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "Liczba nowych wiadomości" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "Ilość nowych wiadomości" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "Jedno powiadomienie dla jednej wiadomości" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(i {0} więcej)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Oznacz jako przeczytana" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Brak tematu" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Włączone" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Nazwa" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Usuń to konto:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "opcjonalny" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "Inny (IMAP)" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "Inny (POP3)" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Konto pocztowe" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Włącz Push-IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "Włącz szyfrowanie SSL" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Nazwa konta:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Typ konta:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Użytkownik:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Hasło:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Serwer:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Port:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Katalogi:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Konfiguracja Mailnag" #: data/config_window.ui.h:2 msgid "General" msgstr "Ogólne" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Konta" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "Wtyczki" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "Dodaj konto" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "Usuń konto" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "Zmodyfikuj konto" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "Zmodyfikuj wtyczkę" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "Konfiguracja wtyczki" mailnag-1.1.0/po/pt.po000066400000000000000000000147101246564717200145430ustar00rootroot00000000000000# 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: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2014-08-03 13:41+0000\n" "Last-Translator: Pedro Beja \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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Sem Nome" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "Número máximo de emails visíveis:" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "Serviço DBus" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "Expor a funcionalidade do Mailnag através do serviço DBus." #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "Notificações de Som" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "Tocar um som quando chegam novos emails." #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "Script de Utilizador" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "Executar um script definido pelo utilizador na receção de emails." #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "remetente" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "assunto" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" "O script seguinte será executado sempre que cheguem novos emails.\n" "Mailnag passa a contagem total de novos emails para este script,\n" "seguido por pares %s." #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "Filtro Spam" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "Filtrar emails indesejados." #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" "Mailnag irá ignorar emails que contenham pelo menos uma \n" "das seguintes palavras no assunto ou remetente." #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "Notificações da LibNotify" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "Mostrar um popup quando chegam novos emails." #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Modo de notificação:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "Quantidade de novos emails" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "Resumo dos novos emails" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "Uma notificação por novo email" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(e mais {0})" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "{0} novos e-mails" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "Novo e-mail" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Marcar como lido" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Sem assunto" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Ativado" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Nome" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Apagar esta conta:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "opcional" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "Outro (IMAP)" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "Outro (POP3)" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Conta de Email" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Ativar Push-IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "Ativar encriptação SSL" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Nome da Conta:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Tipo de conta:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Utilizador:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Password:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Servidor:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Porta:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Pastas:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configuração do Mailnag" #: data/config_window.ui.h:2 msgid "General" msgstr "Geral" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Contas" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "Plugins" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "Adicionar Conta" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "Remover Conta" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "Editar Conta" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "Editar Plugin" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "Configuração de Plugin" mailnag-1.1.0/po/pt_BR.po000066400000000000000000000147331246564717200151330ustar00rootroot00000000000000# 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: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2014-08-03 13:43+0000\n" "Last-Translator: Pedro Beja \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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Sem nome" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "Número máximo de emails visíveis:" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "Serviço DBus" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "Expor a funcionalidade do Mailnag através do serviço DBus." #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "Notificações de Som" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "Tocar um som quando chegam novos emails." #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "Script de Usuário" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "Executar um script definido pelo usuário na receção de emails." #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "remetente" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "assunto" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" "O script seguinte será executado sempre que cheguem novos emails.\n" "Mailnag passa a contagem total de novos emails para este script,\n" "seguido por pares %s." #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "Filtro Spam" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "Filtrar emails indesejados." #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" "Mailnag irá ignorar emails que contenham pelo menos uma \n" "das seguintes palavras no assunto ou remetente." #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "Notificações da LibNotify" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "Mostrar um popup quando chegam novos emails." #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Modo de notificação:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "Quantidade de novos emails" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "Resumo dos novos emails" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "Uma notificação por novo email" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(e mais {0})" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "{0} novos e-mails" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "Novo e-mail" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Marcar como lido" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Sem assunto" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Ativado" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Nome" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Apagar esta conta:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "opcional" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "Outro (IMAP)" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "Outro (POP3)" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Conta de email" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Habilitar Push-IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "Habilitar criptografia SSL" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Nome da conta:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Tipo de conta:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Usuário:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Senha:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Servidor:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Porta:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Pastas:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configuração do Mailnag" #: data/config_window.ui.h:2 msgid "General" msgstr "Geral" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Contas" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "Plugins" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "Adicionar Conta" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "Remover Conta" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "Editar Conta" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "Editar Plugin" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "Configuração de Plugin" mailnag-1.1.0/po/ro.po000066400000000000000000000127071246564717200145440ustar00rootroot00000000000000# 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: 2015-01-31 21:20+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Nedenumit" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(și {0} altele)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Activat" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Nume" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Șterge acest cont:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Cont de email" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Utilizator:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Parola:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Server:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Configurare Mailnag" #: data/config_window.ui.h:2 msgid "General" msgstr "General" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Conturi" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/po/ru.po000066400000000000000000000135441246564717200145520ustar00rootroot00000000000000# 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: 2015-01-31 21:20+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Без названия" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Режим уведомлений:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(и ещё {0})" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Отметить как прочитанное" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Без темы" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Включено" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Название" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Удалить эту учётную запись:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "необязательно" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Учётная запись почты" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Включить Push-IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "Включить SSL шифрование" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Название учётной записи:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Тип учётной записи:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Владелец:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Пароль:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Сервер:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Порт:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Папки:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Настройки Mailnag" #: data/config_window.ui.h:2 msgid "General" msgstr "Общие" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Аккаунты" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/po/sr.po000066400000000000000000000135241246564717200145460ustar00rootroot00000000000000# 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: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2013-06-07 09:23+0000\n" "Last-Translator: Мирослав Николић \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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Неименовано" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Режим обавештавања:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(и још {0})" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Означи као прочитано" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Без теме" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Укључено" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Назив" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Обриши ове налоге:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "необавезно" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Налог поште" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Укључи Гурни-ИМАП" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "Укључи ССЛ шифровање" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Назив налога:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Врста налога:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Корисник:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Лозинка:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Сервер:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Прикључник:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Фасцикле:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Подешавања Поштарка" #: data/config_window.ui.h:2 msgid "General" msgstr "Опште" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Налози" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/po/sv.po000066400000000000000000000146051246564717200145530ustar00rootroot00000000000000# Swedish translation for mailnag # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the mailnag package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: mailnag\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2014-10-04 09:04+0000\n" "Last-Translator: kristian \n" "Language-Team: Swedish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Namnlös" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "Maximalt antal av visade mail:" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "DBus Tjänst" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "Visar Mailnag's funktioner via en DBus tjänst." #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "Ljud Notifieringar" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "Spelar upp ett ljud när nya mail anländer." #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "Användar Kommando" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "Kör ett användardefinerat kommando vid inkommande mail." #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "avsändare" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "ämne" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" "Följande kommando kommer att köras när nya mail anländer.\n" "Mailnag släpper totalsumman av nya mail till detta kommando,\n" "följt av %s par." #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "Skräpfilter" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "Filtrera bort oönskade mail." #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" "Mailnag kommer att ignorera mail som innehåller minst ett av \n" "följande ord i ämne eller avsändare." #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "LibNotify Notifieringar" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "Visar en notifiering på skärmen när nya mail anländer." #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Notifieringsläge:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "Antal av nya mail" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "Summering av nya mail" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "En notifiering per nytt mail" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(och {0} till)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "{0} nytt/nya mail" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "Nya mail" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Markera som läst" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Ämne saknas" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Aktiverad" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Namn" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Ta bort detta konto:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "valfritt" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "Annan (IMAP)" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "Annan (POP3)" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Mail Konto" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Aktivera Push-IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "Aktivera SSL kryptering" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Kontonamn:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Kontotyp:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Användare:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Lösenord:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Server:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Port:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Mappar:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag Inställningar" #: data/config_window.ui.h:2 msgid "General" msgstr "Allmänt" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Konton" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "Tillägg" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "Lägg till Konto" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "Ta bort Konto" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "Redigera Konto" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "Redigera Instick" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "Inställning av Insticksprogram" mailnag-1.1.0/po/tr.po000066400000000000000000000144711246564717200145510ustar00rootroot00000000000000# Turkish translation for mailnag # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2014-07-30 18:44+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" "Language: \n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Adsız" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "Görüntülenecek en fazla mail sayısı" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "DBus Servisi" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "DBus servisi ile Mailnag işlevselliğini ortaya koyar." #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "Sesli Uyarı" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "Yeni mail geldiğinde ses çal." #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "Kullanıcı Script" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "Mail geldiğinde kullanıcı tanımlı scripti çalıştır." #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "gönderen" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "konu" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" "Belirtilen script yeni mail alındığında çalıştırılacaktır.\n" "Mailnag bu script %s ile eşleşenler için\n" "toplam mail sayısını tutmaz." #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "Spam filtresi" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "İstenmeyen postaları filtreler." #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" "Mailnag belirtilen kelimelerin konu veya gönderende\n" "geçmesi durumunda mailleri görmezden gelecektir." #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "LibNotify Uyarıları" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "Yeni mail geldiğinde pencerede göster." #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Uyarı kipi:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "Yeni mail sayısı" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "Yeni mail özeti" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "Her yeni mail için bir uyarı" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "ve {0} fazlası" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Okundu olarak işaretle" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Konusuz" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Etkin" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Adı" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Bu hesabı sil:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "opsiyonel" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "Diğer (IMAP)" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "Diğer (POP3)" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Mail Hesabı" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Push-IMAP Etkin" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "SSL şifreleme Erkin" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Hesap adı:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Hesap türü:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Kullanıcı:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Parola:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Sunucu:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Port:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Klasörler:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag Ayarları" #: data/config_window.ui.h:2 msgid "General" msgstr "Genel" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Hesaplar" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "Eklentiler" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "Hesap Ekle" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "Hesabı Sil" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "Hesabı Düzenle" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "Eklentiyi Düzenle" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "Eklenti Ayarları" mailnag-1.1.0/po/uk.po000066400000000000000000000163161246564717200145430ustar00rootroot00000000000000# 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: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2015-01-30 17:58+0000\n" "Last-Translator: Oleg «Eleidan» Kulyk \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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Без назви" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "Максимальна к-сть листів у списку:" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "Служба DBus" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "Забезпечує синхронізацію зі службою DBus." #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "Звукові сповіщення" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "Супроводжує нові листи звуковим сповіщенням." #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "Користувацький скріпт" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "Виконувати скріпт користувача при отриманні листа." #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "відправник" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "тема" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" "Вказаний скріпт буде виконуватись при появі нового листа.\n" "Mailnag передає скріпту загальну к-сть листів,\n" "та значення %s." #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "Фільтр спаму" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "Відфільтровує небажані листі." #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" "Mailnag буде ігнорувати листи, що містять в темі\n" "чи імені відправника хоча б одне слово з переліку ." #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "Сповіщення LibNotify" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "Сповіщає про нові листи за допомогою випливаючих повідомлень." #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "Метод сповіщення:" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "Кількість нових листів" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "Підсумок" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "Про кожен лист окремо" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(і ще {0})" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "{0} листів" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "Новий лист" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "Позначити як прочитане" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "Без теми" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Задіяно" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Назва" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Видалити обліковий запис" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "(необов’язково)" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "Інше (IMAP)" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "Інше (POP3)" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Поштовий обліковий запис" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "Задіяти Push-IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "Задіяти SSL-шифрування" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Назва пошти" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "Поштовий сервіс:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Користувач:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Пароль:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Сервер:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "Порт" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "Теки:" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Налаштування Mailnag" #: data/config_window.ui.h:2 msgid "General" msgstr "Загальне" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Облікові записи" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "Додатки" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "Додати обліковий запис" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "Видалити обліковий запис" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "Редагувати обліковий запис" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "Налаштувати додаток" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "Налаштування додатка" mailnag-1.1.0/po/vi.po000066400000000000000000000127671246564717200145500ustar00rootroot00000000000000# 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: 2015-01-31 21:20+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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "Chưa có tên" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "(và {0} nữa)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "Bật" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "Tên" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "Xóa tài khoản này:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "Tài khoản thư" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "Tên tài khoản:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "Người dùng:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "Mật khẩu:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "Máy chủ" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Cấu hình Mailnag" #: data/config_window.ui.h:2 msgid "General" msgstr "Chung" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "Tài khoản" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/po/zh_CN.po000066400000000000000000000137021246564717200151210ustar00rootroot00000000000000# 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: 2015-01-31 21:20+0100\n" "PO-Revision-Date: 2014-07-14 11:16+0000\n" "Last-Translator: Hu Meng \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: 2015-02-01 14:16+0000\n" "X-Generator: Launchpad (build 17306)\n" #: Mailnag/common/accounts.py:49 msgid "Unnamed" msgstr "未命名" #: Mailnag/plugins/unityplugin.py:98 msgid "Ubuntu Unity" msgstr "" #: Mailnag/plugins/unityplugin.py:99 msgid "Shows new mails in Ubuntu's Messaging menu." msgstr "" #: Mailnag/plugins/unityplugin.py:118 Mailnag/plugins/libnotifyplugin.py:160 msgid "Maximum number of visible mails:" msgstr "可显示邮件的最大数量" #: Mailnag/plugins/goaplugin.py:92 msgid "GNOME Online Accounts" msgstr "" #: Mailnag/plugins/goaplugin.py:93 msgid "GNOME Online Accounts Integration." msgstr "" #: Mailnag/plugins/dbusplugin.py:83 msgid "DBus Service" msgstr "DBus服务" #: Mailnag/plugins/dbusplugin.py:84 msgid "Exposes Mailnag's functionality via a DBus service." msgstr "" #: Mailnag/plugins/soundplugin.py:65 msgid "Sound Notifications" msgstr "声音通知" #: Mailnag/plugins/soundplugin.py:66 msgid "Plays a sound when new mails arrive." msgstr "当新邮件到达时播放声音。" #: Mailnag/plugins/userscriptplugin.py:62 msgid "User Script" msgstr "用户脚本" #: Mailnag/plugins/userscriptplugin.py:63 msgid "Runs an user defined script on mail arrival." msgstr "运行在邮件到达时的用户自定义脚本。" #: Mailnag/plugins/userscriptplugin.py:83 msgid "sender" msgstr "寄件人" #: Mailnag/plugins/userscriptplugin.py:83 msgid "subject" msgstr "主题" #: Mailnag/plugins/userscriptplugin.py:84 #, python-format 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 %s pairs." msgstr "" #: Mailnag/plugins/spamfilterplugin.py:69 msgid "Spam Filter" msgstr "垃圾邮件过滤器" #: Mailnag/plugins/spamfilterplugin.py:70 msgid "Filters out unwanted mails." msgstr "过滤掉不想要的邮件。" #: Mailnag/plugins/spamfilterplugin.py:90 msgid "" "Mailnag will ignore mails containing at least one of \n" "the following words in subject or sender." msgstr "" #: Mailnag/plugins/libnotifyplugin.py:116 msgid "LibNotify Notifications" msgstr "弹出窗口通知" #: Mailnag/plugins/libnotifyplugin.py:117 msgid "Shows a popup when new mails arrive." msgstr "当新邮件到达时弹出。" #: Mailnag/plugins/libnotifyplugin.py:137 msgid "Notification mode:" msgstr "通知模式" #: Mailnag/plugins/libnotifyplugin.py:145 msgid "Count of new mails" msgstr "新邮件数量" #: Mailnag/plugins/libnotifyplugin.py:148 msgid "Summary of new mails" msgstr "新邮件摘要" #: Mailnag/plugins/libnotifyplugin.py:151 msgid "One notification per new mail" msgstr "每封新邮件通知一次" #: Mailnag/plugins/libnotifyplugin.py:249 #: Mailnag/plugins/libnotifyplugin.py:251 #, python-brace-format msgid "(and {0} more)" msgstr "( 还有 {0} 条)" #: Mailnag/plugins/libnotifyplugin.py:254 #: Mailnag/plugins/libnotifyplugin.py:282 #, python-brace-format msgid "{0} new mails" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:256 #: Mailnag/plugins/libnotifyplugin.py:284 msgid "New mail" msgstr "" #: Mailnag/plugins/libnotifyplugin.py:271 msgid "Mark as read" msgstr "标记为已读" #: Mailnag/daemon/mails.py:170 msgid "No subject" msgstr "无主题" #: Mailnag/configuration/configwindow.py:108 #: Mailnag/configuration/configwindow.py:133 msgid "Enabled" msgstr "已启用" #: Mailnag/configuration/configwindow.py:114 #: Mailnag/configuration/configwindow.py:140 msgid "Name" msgstr "名称" #: Mailnag/configuration/configwindow.py:346 msgid "Delete this account:" msgstr "删除该帐户:" #: Mailnag/configuration/accountdialog.py:75 #: Mailnag/configuration/accountdialog.py:76 msgid "optional" msgstr "可选" #: Mailnag/configuration/accountdialog.py:141 msgid "Other (IMAP)" msgstr "其他(IMAP)" #: Mailnag/configuration/accountdialog.py:142 msgid "Other (POP3)" msgstr "其他(POP3)" #: data/account_dialog.ui.h:1 msgid "Mail Account" msgstr "邮件账户" #: data/account_dialog.ui.h:2 msgid "Enable Push-IMAP" msgstr "启用 Push-IMAP" #: data/account_dialog.ui.h:3 msgid "Enable SSL encryption" msgstr "启用 SSL 加密" #: data/account_dialog.ui.h:4 msgid "Accountname:" msgstr "账户名称:" #: data/account_dialog.ui.h:5 msgid "Account type:" msgstr "账户类型:" #: data/account_dialog.ui.h:6 msgid "User:" msgstr "用户名:" #: data/account_dialog.ui.h:7 msgid "Password:" msgstr "密码:" #: data/account_dialog.ui.h:8 msgid "Server:" msgstr "服务器:" #: data/account_dialog.ui.h:9 msgid "Port:" msgstr "端口:" #: data/account_dialog.ui.h:10 msgid "Folders:" msgstr "" #: data/config_window.ui.h:1 msgid "Mailnag Configuration" msgstr "Mailnag 配置" #: data/config_window.ui.h:2 msgid "General" msgstr "基本设置" #: data/config_window.ui.h:3 msgid "Accounts" msgstr "帐户" #: data/config_window.ui.h:4 msgid "Plugins" msgstr "" #: data/config_window.ui.h:5 msgid "" "Mailnag - An extensible mail " "notification daemon.\n" "Copyright (c) 2011 - 2015 Patrick Ulbrich\n" "and contributors." msgstr "" #: data/config_window.ui.h:8 msgid "Add Account" msgstr "" #: data/config_window.ui.h:9 msgid "Remove Account" msgstr "" #: data/config_window.ui.h:10 msgid "Edit Account" msgstr "" #: data/config_window.ui.h:11 msgid "Edit Plugin" msgstr "" #: data/plugin_dialog.ui.h:1 msgid "Plugin Configuration" msgstr "" mailnag-1.1.0/setup.py000077500000000000000000000077351246564717200146700ustar00rootroot00000000000000#!/usr/bin/env python2 # 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('./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')) self._patch_file(os.path.join(BUILD_PATCH_DIR, 'common/dist_cfg.py'), os.path.join(BUILD_PATCH_DIR, 'common/dist_cfg.py'), './Mailnag', os.path.join(PREFIX, INSTALL_LIB_DIR)) self._patch_file(os.path.join(BUILD_PATCH_DIR, 'common/dist_cfg.py'), os.path.join(BUILD_PATCH_DIR, 'common/dist_cfg.py'), "'.'", "'%s'" % os.path.join(PREFIX, 'bin')) 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() self._add_icon_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])) def _add_icon_data(self): for root, dirs, files in os.walk('data/icons'): for file in files: src_path = os.path.join(root, file) dst_path = os.path.join('share/icons', os.path.dirname(src_path[len('data/icons')+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='An extensible mail notification daemon', author='Patrick Ulbrich', author_email='zulu99@gmx.net', url='https://github.com/pulb/mailnag', license='GNU GPL2', package_dir = {'Mailnag.common' : os.path.join(BUILD_PATCH_DIR, 'common')}, packages=['Mailnag', 'Mailnag.common', 'Mailnag.configuration', 'Mailnag.daemon', 'Mailnag.plugins'], scripts=['mailnag', 'mailnag-config'], data_files=[('share/mailnag', glob.glob('data/*.ui')), ('share/mailnag', ['data/config_window.css']), ('share/mailnag', ['data/mailnag.ogg']), ('share/mailnag', ['data/mailnag.png']), ('share/applications', [os.path.join(BUILD_PATCH_DIR, 'mailnag-config.desktop')])], cmdclass={'build': BuildData, 'install_data': InstallData, 'uninstall': Uninstall} )