diff --git a/AUTHORS b/AUTHORS index 41f09b3..fbc1546 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,8 +16,10 @@ Code, docs and packaging contributors: Amin Bandali Edwin Smulders Hasan Yavuz Özderya +Heiko Adams Leighton Earl Matthias Mailänder +Oleg Taylor Braun-Jones Thorsten Leemhuis Thomas Haider @@ -63,6 +65,7 @@ Manuel Xosé Lemos Marcos Lans Marti Bosch Mattia Meneguzzo +Микола Ткач Мирослав Николић Oleg «Eleidan» Kulyk Patrick Ulbrich diff --git a/Mailnag/common/accounts.py b/Mailnag/common/accounts.py index 8d87172..a5c5bec 100644 --- a/Mailnag/common/accounts.py +++ b/Mailnag/common/accounts.py @@ -23,8 +23,10 @@ # MA 02110-1301, USA. # +import re import poplib import logging +import json import Mailnag.common.imaplib2 as imaplib from Mailnag.common.utils import splitstr @@ -38,7 +40,7 @@ account_defaults = { 'ssl' : '1', 'imap' : '1', 'idle' : '1', - 'folder' : '' + 'folder' : '[]' } CREDENTIAL_KEY = 'Mailnag password for %s://%s@%s' @@ -105,22 +107,14 @@ class Account: # conn.close() # allowed in SELECTED state only conn.logout() - separators = [ ' "/" ', ' "." ' ] for d in data: - folder = '' - for s in separators: - if s in d: - folder = d.split(s)[-1] - break - - if len(folder) == 0: + match = re.match('.+\s+("."|"?NIL"?)\s+"?([^"]+)"?$', d) + + if match == None: logging.warning("Folder format not supported.") - break - - if (folder[0] == '"') and (folder[-1] == '"'): - folder = folder[1:-1] - - lst.append(folder) + else: + folder = match.group(2) + lst.append(folder) return lst @@ -283,7 +277,11 @@ class AccountManager: 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') )) - folders = splitstr(self._get_account_cfg(cfg, section_name, 'folder'), ',') + folders_str = self._get_account_cfg(cfg, section_name, 'folder') + if re.match(r'^\[.*\]$', folders_str): + folders = json.loads(folders_str) + else: + folders = splitstr(folders_str, ',') if self._credentialstore != None: protocol = 'imap' if imap else 'pop' @@ -336,7 +334,7 @@ class AccountManager: 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', ', '.join(acc.folders)) + cfg.set(section_name, 'folder', json.dumps(acc.folders)) if self._credentialstore != None: protocol = 'imap' if acc.imap else 'pop' diff --git a/Mailnag/common/mutf7.py b/Mailnag/common/mutf7.py new file mode 100644 index 0000000..96408bd --- /dev/null +++ b/Mailnag/common/mutf7.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +''' +This awesome piece of code is intented to encode and decode modified UTF-7 +(the one that is used for IMAP folder names) + +encode_mutf7(text) - to encode +decode_mutf7(text) - to decode +''' + +__author__ = "https://github.com/cheshire-mouse" +__license__ = "WTFPL v. 2" + +import base64 +import re + +ascii_codes = set(range(0x20,0x7f)) + +def __get_ascii(text): + pos = 0 + for c in text: + code=ord(c) + if ord(c) not in ascii_codes : + break + pos += 1 + return text[:pos].encode('ascii') + +def __remove_ascii(text): + pos = 0 + for c in text: + if ord(c) not in ascii_codes : + break + pos += 1 + return text[pos:] + +def __get_nonascii(text): + pos = 0 + for c in text: + code=ord(c) + if ord(c) in ascii_codes : + break + pos += 1 + return text[:pos] + +def __remove_nonascii(text): + pos = 0 + for c in text: + if ord(c) in ascii_codes : + break + pos += 1 + return text[pos:] + +def __encode_modified_utf7(text): + #modified base64 - good old base64 without padding characters (=) + result = base64.b64encode(text.encode('utf-16be')).rstrip('=') + result = result.replace('/',',') + result = '&' + result + '-' + return result + +def encode_mutf7(text): + result = "" + text = text.replace('&','&-') + while len(text) > 0: + result += __get_ascii(text) + text = __remove_ascii(text) + if len(text) > 0: + result += __encode_modified_utf7(__get_nonascii(text)) + text = __remove_nonascii(text) + return result + +def __decode_modified_utf7(text): + if text == '&-': + return '&' + #remove leading & and trailing - + text_mb64 = text[1:-1] + text_b64 = text_mb64.replace(',','/') + #back to normal base64 with padding + while len(text_b64) % 4 != 0: + text_b64 += '=' + text_u16 = base64.b64decode(text_b64) + result = text_u16.decode('utf-16be') + return result + +def decode_mutf7(text): + rxp = re.compile('&[^&-]*-') + match = rxp.search(text) + while ( match ): + encoded_text = match.group(0) + decoded_text = __decode_modified_utf7(encoded_text) + text = rxp.sub(decoded_text,text, count=1) + match = rxp.search(text) + result = text + return result + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 + diff --git a/Mailnag/common/utils.py b/Mailnag/common/utils.py index 6c6af04..1e6938f 100644 --- a/Mailnag/common/utils.py +++ b/Mailnag/common/utils.py @@ -28,10 +28,34 @@ import sys import time import dbus import logging +import logging.handlers import inspect from Mailnag.common.dist_cfg import PACKAGE_NAME, DBUS_BUS_NAME, DBUS_OBJ_PATH +LOG_FORMAT = '%(levelname)s (%(asctime)s): %(message)s' +LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' + + +def init_logging(enable_stdout = True, enable_syslog = True, log_level = logging.DEBUG): + logging.basicConfig( + format = LOG_FORMAT, + datefmt = LOG_DATE_FORMAT, + level = log_level) + + logger = logging.getLogger('') + + if not enable_stdout: + stdout_handler = logger.handlers[0] + logger.removeHandler(stdout_handler) + + if enable_syslog: + syslog_handler = logging.handlers.SysLogHandler(address='/dev/log') + syslog_handler.setLevel(log_level) + syslog_handler.setFormatter(logging.Formatter(LOG_FORMAT, LOG_DATE_FORMAT)) + + logger.addHandler(syslog_handler) + def get_data_paths(): # Add "./data" in workdir for running from builddir diff --git a/Mailnag/configuration/accountdialog.py b/Mailnag/configuration/accountdialog.py index 358cebc..8eb9fac 100644 --- a/Mailnag/configuration/accountdialog.py +++ b/Mailnag/configuration/accountdialog.py @@ -31,6 +31,7 @@ from Mailnag.common.dist_cfg import PACKAGE_NAME from Mailnag.common.i18n import _ from Mailnag.common.utils import get_data_file, splitstr from Mailnag.common.accounts import Account +from Mailnag.common import mutf7 IDX_GMAIL = 0 IDX_GMX = 1 @@ -179,7 +180,7 @@ class AccountDialog: folders = [] for row in self._liststore_folders: if row[0]: - folders.append(row[1]) + folders.append(mutf7.encode_mutf7(row[1].decode('utf-8'))) return folders @@ -283,7 +284,7 @@ class AccountDialog: if f in self._acc.folders: enabled = True self._selected_folder_count += 1 - row = [enabled, f] + row = [enabled, mutf7.decode_mutf7(f)] self._liststore_folders.append(row) # Enable the push checkbox in case a remote folder wasn't found diff --git a/Mailnag/daemon/conntest.py b/Mailnag/daemon/conntest.py index da91d5c..4209661 100644 --- a/Mailnag/daemon/conntest.py +++ b/Mailnag/daemon/conntest.py @@ -3,7 +3,7 @@ # # conntest.py # -# Copyright 2014 Patrick Ulbrich +# Copyright 2014, 2016 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 @@ -24,7 +24,7 @@ import os import dbus -PING_TEST_HOST = 'www.google.de' +PING_TEST_HOST = 'www.google.com' NM_STATE_CONNECTED_GLOBAL = 70 diff --git a/README.md b/README.md index a6caca8..b41647b 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ 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. +Mailnag is available in Debian stable and unstable. Run `sudo apt-get install mailnag` in a terminal to install it. ### Fedora diff --git a/mailnag b/mailnag index 59113b3..c237632 100755 --- a/mailnag +++ b/mailnag @@ -31,7 +31,6 @@ from dbus.mainloop.glib import DBusGMainLoop import threading import argparse import logging -import logging.handlers import os import signal @@ -41,16 +40,14 @@ fix_cwd() 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 +from Mailnag.common.utils import set_procname, init_logging, shutdown_existing_instance 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() @@ -81,27 +78,7 @@ def get_args(): 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: @@ -127,7 +104,8 @@ def main(): # Note: don't start logging before an existing Mailnag # instance has been shut down completely (will corrupt logfile). - init_logging(not args.quiet) + init_logging(enable_stdout = (not args.quiet), \ + enable_syslog = True, log_level = LOG_LEVEL) try: if not cfg_exists(): diff --git a/mailnag-config b/mailnag-config index 4032036..14aa768 100755 --- a/mailnag-config +++ b/mailnag-config @@ -27,9 +27,10 @@ gi.require_version('GLib', '2.0') import os import subprocess +import logging from gi.repository import Gtk, Gio -from Mailnag.common.utils import fix_cwd +from Mailnag.common.utils import fix_cwd, init_logging fix_cwd() @@ -38,6 +39,8 @@ from Mailnag.common.utils import set_procname, shutdown_existing_instance, get_d from Mailnag.common.dist_cfg import BIN_DIR, PACKAGE_NAME from Mailnag.configuration.configwindow import ConfigWindow +LOG_LEVEL = logging.DEBUG + class App(Gtk.Application): def __init__(self): @@ -115,7 +118,8 @@ class App(Gtk.Application): def main(): set_procname("mailnag-config") - + init_logging(enable_stdout = True, enable_syslog = False, log_level = LOG_LEVEL) + app = App() app.run(None) diff --git a/po/ru.po b/po/ru.po new file mode 100644 index 0000000..9f4fd98 --- /dev/null +++ b/po/ru.po @@ -0,0 +1,284 @@ +# Mailnag russian tranlation +# This file is distributed under the same license as the mailnag package. +# Oleg , 2016. +# +msgid "" +msgstr "" +"Project-Id-Version: mailnag\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-02-03 19:08+0100\n" +"PO-Revision-Date: 2016-08-17 2O:00+0300\n" +"Last-Translator: Oleg \n" +"Language: RU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: 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 "account" +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 sequences." +msgstr "" +"Следующий скрипт будет выполнен при появлении новых сообщений.\n" +"Скрипту передаётся количество сообщений,\n" +"за которым следуют: %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 "Отображает новые письма в меню сообщений Ubuntu." + +#: Mailnag/plugins/unityplugin.py:118 +msgid "Maximum number of visible mails:" +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 проигнорирует письмо, если имя отправителя\n" +"или тема содержит одно из следующих слов" + +#: Mailnag/plugins/libnotifyplugin.py:100 +msgid "LibNotify Notifications" +msgstr "Уведомления LibNotify" + +#: Mailnag/plugins/libnotifyplugin.py:101 +msgid "Shows a popup when new mails arrive." +msgstr "Отображение всплывающего уведомления при получении новых писем" + +#: Mailnag/plugins/libnotifyplugin.py:117 +msgid "Count of new mails" +msgstr "Количество новых писем" + +#: Mailnag/plugins/libnotifyplugin.py:118 +msgid "Short summary of new mails" +msgstr "Краткие сведения о письмах" + +#: Mailnag/plugins/libnotifyplugin.py:119 +msgid "Detailed summary of new mails" +msgstr "Подробные сведения о письмах" + +#: Mailnag/plugins/libnotifyplugin.py:120 +msgid "One notification per new mail" +msgstr "Одно уведомление на письмо" + +#: Mailnag/plugins/libnotifyplugin.py:128 +msgid "Notification mode:" +msgstr "Тип уведомлений:" + +#: Mailnag/plugins/libnotifyplugin.py:215 +#: Mailnag/plugins/libnotifyplugin.py:251 +#: Mailnag/plugins/libnotifyplugin.py:279 +#, python-brace-format +msgid "{0} new mails" +msgstr "сообщений: {0}" + +#: Mailnag/plugins/libnotifyplugin.py:217 +#, python-brace-format +msgid "from {0} and others." +msgstr "от {0} и других." + +#: Mailnag/plugins/libnotifyplugin.py:219 +#: Mailnag/plugins/libnotifyplugin.py:222 +#, python-brace-format +msgid "from {0}." +msgstr "от {0}." + +#: Mailnag/plugins/libnotifyplugin.py:221 +#: Mailnag/plugins/libnotifyplugin.py:253 +#: Mailnag/plugins/libnotifyplugin.py:281 +msgid "New mail" +msgstr "Новое сообщение" + +#: Mailnag/plugins/libnotifyplugin.py:246 +#: Mailnag/plugins/libnotifyplugin.py:248 +#, python-brace-format +msgid "(and {0} more)" +msgstr "(и еще {0})" + +#: Mailnag/plugins/libnotifyplugin.py:268 +msgid "Mark as read" +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/goaplugin.py:93 +msgid "GNOME Online Accounts" +msgstr "" + +#: Mailnag/plugins/goaplugin.py:94 +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 через службу DBus." + +#: Mailnag/daemon/mails.py:171 +msgid "No subject" +msgstr "Без темы" + +#: Mailnag/configuration/plugindialog.py:33 +msgid "Plugin Configuration" +msgstr "Настройка плагина" + +#: Mailnag/configuration/configwindow.py:109 +#: Mailnag/configuration/configwindow.py:134 +#: Mailnag/configuration/accountdialog.py:88 +msgid "Enabled" +msgstr "Включено" + +#: Mailnag/configuration/configwindow.py:115 +#: Mailnag/configuration/configwindow.py:141 +#: Mailnag/configuration/accountdialog.py:94 +msgid "Name" +msgstr "Имя" + +#: Mailnag/configuration/configwindow.py:344 +msgid "Delete this account:" +msgstr "Удалить учетную запись:" + +#: Mailnag/configuration/accountdialog.py:84 +msgid "optional" +msgstr "необязательно" + +#: Mailnag/configuration/accountdialog.py:177 +msgid "Other (IMAP)" +msgstr "Другое (IMAP)" + +#: Mailnag/configuration/accountdialog.py:178 +msgid "Other (POP3)" +msgstr "Другое (POP3)" + +#: Mailnag/configuration/accountdialog.py:271 +msgid "Connection failed." +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 (optional)" +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 - 2016 Patrick Ulbrich\n" +"and contributors." +msgstr "" +"Mailnag - служба уведомлений " +"о входящей почте.\n" +"Copyright (c) 2011 - 2016 Patrick Ulbrich\n" +"and contributors." + +#: 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 "Редактировать плагин"